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

# Crews

> Manage crews -- isolated agent teams with their own containers, network policies, and members.

Crews are isolated agent teams, each with its own container resources, network policy, and members. A crew bundles the runtime sandbox (memory/CPU/TTL, runtime image, devcontainer/mise config), the egress posture (`free` vs `restricted` with an allowed-domain list), an autonomy policy, an optional shared persona, and cross-crew connections that gate agent-to-agent messaging.

All crew endpoints require authentication and workspace context (the `workspace_id` query parameter).

## Endpoints

| Method      | Endpoint                                                                | Purpose                             |
| ----------- | ----------------------------------------------------------------------- | ----------------------------------- |
| GET         | [`/api/v1/crews`](#list-crews)                                          | List crews in a workspace           |
| POST        | [`/api/v1/crews`](#create-crew)                                         | Create a crew                       |
| GET         | [`/api/v1/crews/{crewId}`](#get-crew)                                   | Get a single crew                   |
| PATCH / PUT | [`/api/v1/crews/{crewId}`](#update-crew)                                | Update a crew                       |
| DELETE      | [`/api/v1/crews/{crewId}`](#delete-crew)                                | Soft-delete a crew                  |
| GET         | [`/api/v1/crews/{crewId}/members`](#list-members)                       | List crew members                   |
| POST        | [`/api/v1/crews/{crewId}/members`](#add-member)                         | Add a crew member                   |
| PATCH       | [`/api/v1/crews/{crewId}/members/{memberId}`](#update-member-role)      | Update a member's per-crew role     |
| DELETE      | [`/api/v1/crews/{crewId}/members/{memberId}`](#remove-member)           | Remove a crew member                |
| GET         | [`/api/v1/crews/{crewId}/persona`](#get-crew-persona)                   | Get the crew persona                |
| PUT         | [`/api/v1/crews/{crewId}/persona`](#set-crew-persona)                   | Set the crew persona                |
| DELETE      | [`/api/v1/crews/{crewId}/persona`](#reset-crew-persona)                 | Reset the crew persona              |
| GET         | [`/api/v1/crews/{crewId}/policy`](#get-crew-policy)                     | Get the crew autonomy policy        |
| PUT         | [`/api/v1/crews/{crewId}/policy`](#set-crew-policy)                     | Set the crew autonomy policy        |
| POST        | [`/api/v1/crews/{crewId}/apply-avatar-style`](#apply-avatar-style)      | Apply an avatar style across agents |
| GET         | [`/api/v1/crew-connections`](#list-connections)                         | List crew connections               |
| POST        | [`/api/v1/crew-connections`](#create-connection)                        | Create a crew connection            |
| DELETE      | [`/api/v1/crew-connections/{connectionId}`](#delete-connection)         | Delete a crew connection            |
| GET         | [`/api/v1/crews/{crewId}/files`](#crew-files-proxy)                     | List files in the crew workspace    |
| GET         | [`/api/v1/crews/{crewId}/files/download`](#crew-files-proxy)            | Download a file                     |
| PUT         | [`/api/v1/crews/{crewId}/files/save`](#crew-files-proxy)                | Save/upload a file                  |
| GET         | [`/api/v1/crews/{crewId}/assignments`](#crew-assignments)               | List task assignments               |
| GET         | [`/api/v1/crews/{crewId}/peer-conversations`](#crew-peer-conversations) | List peer conversations             |
| GET         | [`/api/v1/crews/{crewId}/standup`](#crew-standup)                       | Get the crew standup summary        |
| GET         | [`/api/v1/crews/{crewId}/escalations`](#list-escalations)               | List crew escalations               |
| PATCH       | [`/api/v1/escalations/{escalationId}/resolve`](#resolve-escalation)     | Resolve an escalation               |
| GET         | [`/api/v1/escalations/pending-count`](#pending-escalation-count)        | Count pending escalations           |

***

## Crew CRUD

Create, read, update, and soft-delete crews and their container/network configuration.

### List Crews

```
GET /api/v1/crews?workspace_id={workspaceId}
```

Returns all crews in the workspace, ordered by creation date descending.

**Auth:** Session or CLI token + workspace membership

**Response:** `200 OK`

```json theme={null}
[
  {
    "id": "cm1abc123",
    "workspace_id": "ws_123",
    "name": "Engineering",
    "slug": "engineering",
    "description": "Core engineering team",
    "color": "blue",
    "icon": "code",
    "avatar_style": "bottts",
    "container_memory_mb": 4096,
    "container_cpus": 2.0,
    "container_ttl_hours": 24,
    "network_mode": "restricted",
    "allowed_domains": ["api.github.com", "registry.npmjs.org"],
    "mcp_config_json": null,
    "escalation_config": null,
    "issue_prefix": "ENG",
    "created_at": "2024-01-15T10:00:00Z",
    "updated_at": "2024-01-15T10:00:00Z",
    "_count": {
      "agents": 3,
      "members": 2
    }
  }
]
```

### Response Fields

| Field                  | Type      | Description                                                                            |
| ---------------------- | --------- | -------------------------------------------------------------------------------------- |
| `id`                   | string    | Crew ID                                                                                |
| `workspace_id`         | string    | Parent workspace ID                                                                    |
| `name`                 | string    | Display name                                                                           |
| `slug`                 | string    | URL-safe identifier                                                                    |
| `description`          | string?   | Crew description                                                                       |
| `color`                | string?   | Palette color ID                                                                       |
| `icon`                 | string?   | Lucide icon name                                                                       |
| `avatar_style`         | string?   | Avatar style for agents (e.g., `"bottts"`)                                             |
| `container_memory_mb`  | integer   | Container memory limit in MB                                                           |
| `container_cpus`       | number    | Container CPU limit                                                                    |
| `container_ttl_hours`  | integer?  | Container TTL in hours                                                                 |
| `network_mode`         | string    | `"free"` or `"restricted"`                                                             |
| `allowed_domains`      | string\[] | Allowed domains (restricted mode only)                                                 |
| `mcp_config_json`      | string?   | Raw MCP server config JSON                                                             |
| `escalation_config`    | string?   | Escalation policy config JSON                                                          |
| `runtime_image`        | string?   | Custom runtime image (e.g., `ghcr.io/org/runtime:1.0`)                                 |
| `devcontainer_config`  | string?   | Raw devcontainer.json (max 100 KB)                                                     |
| `mise_config`          | string?   | Raw mise config TOML (max 10 KB)                                                       |
| `services_json`        | string?   | Sidecar services JSON (max 64 KB)                                                      |
| `cached_image`         | string?   | Locally built image tag (set by provisioner)                                           |
| `config_hash`          | string?   | Hash of devcontainer/mise/runtime inputs used for `cached_image`                       |
| `max_ephemeral_agents` | integer   | Per-crew quota enforced by the hire flow; always present on List/Get/Update responses  |
| `issue_prefix`         | string?   | Issue identifier prefix (e.g., `"ENG"`); always `null` on List (only populated by Get) |
| `created_at`           | string    | ISO 8601 timestamp                                                                     |
| `updated_at`           | string    | ISO 8601 timestamp                                                                     |
| `_count.agents`        | integer   | Number of active agents                                                                |
| `_count.members`       | integer   | Number of crew members                                                                 |

***

### Create Crew

```
POST /api/v1/crews?workspace_id={workspaceId}
```

**Auth:** Session or CLI token + `OWNER`, `ADMIN`, or `MANAGER` role

**Request Body:**

| Field                 | Type      | Required | Default  | Description                                                                          |
| --------------------- | --------- | -------- | -------- | ------------------------------------------------------------------------------------ |
| `name`                | string    | Yes      | --       | Display name (2-100 characters)                                                      |
| `slug`                | string    | Yes      | --       | URL-safe identifier (2-50 chars, `^[a-z0-9][a-z0-9_-]*$`)                            |
| `description`         | string    | No       | `null`   | Crew description                                                                     |
| `color`               | string    | No       | `null`   | Palette ID (`blue`, `emerald`, `violet`, `amber`, `rose`, `cyan`, `lime`, `fuchsia`) |
| `icon`                | string    | No       | `null`   | Lucide icon name (`code`, `rocket`, `clipboard`, etc.)                               |
| `container_memory_mb` | integer   | No       | `4096`   | Container memory limit in MB                                                         |
| `container_cpus`      | number    | No       | `2.0`    | Container CPU limit                                                                  |
| `container_ttl_hours` | integer   | No       | `null`   | Container time-to-live in hours                                                      |
| `network_mode`        | string    | No       | `"free"` | `"free"` or `"restricted"`                                                           |
| `allowed_domains`     | string\[] | No       | `[]`     | Allowed domains when `network_mode` is `"restricted"` (ignored otherwise)            |
| `runtime_image`       | string    | No       | `null`   | Custom runtime image; existence is validated against the registry before insert      |
| `devcontainer_config` | string    | No       | `null`   | Raw devcontainer.json (max 100 KB; parsed for syntax)                                |
| `mise_config`         | string    | No       | `null`   | Raw mise config TOML (max 10 KB; parsed for syntax)                                  |
| `services_json`       | string    | No       | `null`   | Sidecar services JSON (max 64 KB; structurally validated)                            |

```json theme={null}
{
  "name": "Engineering",
  "slug": "engineering",
  "description": "Core engineering team",
  "color": "blue",
  "icon": "code",
  "container_memory_mb": 4096,
  "container_cpus": 2.0,
  "network_mode": "restricted",
  "allowed_domains": ["api.github.com", "registry.npmjs.org"]
}
```

**Response:** `201 Created` -- returns the created crew object (same shape as List response item).

**Error Responses:**

| Status | Condition                                                                                                                                                                                        |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400`  | Invalid name/slug length or format, invalid `network_mode`, invalid domain entry, `runtime_image` not found in registry, oversize or invalid `devcontainer_config`/`mise_config`/`services_json` |
| `402`  | License crew limit exceeded                                                                                                                                                                      |
| `403`  | Insufficient role                                                                                                                                                                                |
| `409`  | Slug already taken in this workspace                                                                                                                                                             |

***

### Get Crew

```
GET /api/v1/crews/{crewId}?workspace_id={workspaceId}
```

**Auth:** Session or CLI token + workspace membership

| Path Parameter | Description |
| -------------- | ----------- |
| `crewId`       | Crew ID     |

**Response:** `200 OK` -- full crew object including `_count`, `issue_prefix`, `mcp_config_json`, and `escalation_config`.

| Status | Condition                                      |
| ------ | ---------------------------------------------- |
| `404`  | Crew not found or does not belong to workspace |

***

### Update Crew

```
PATCH /api/v1/crews/{crewId}?workspace_id={workspaceId}
PUT /api/v1/crews/{crewId}?workspace_id={workspaceId}
```

Both `PATCH` and `PUT` are supported and behave identically (partial update -- only provided fields are changed).

**Auth:** Session or CLI token + `OWNER` or `ADMIN` role

**Request Body:** All fields are optional.

| Field                  | Type      | Description                                                                                         |
| ---------------------- | --------- | --------------------------------------------------------------------------------------------------- |
| `name`                 | string    | Display name (2-100 characters)                                                                     |
| `slug`                 | string    | URL-safe identifier (2-50 characters)                                                               |
| `description`          | string    | Crew description                                                                                    |
| `color`                | string    | Palette ID                                                                                          |
| `icon`                 | string    | Lucide icon name                                                                                    |
| `avatar_style`         | string    | Avatar style for agents                                                                             |
| `container_memory_mb`  | integer   | Container memory limit in MB                                                                        |
| `container_cpus`       | number    | Container CPU limit                                                                                 |
| `container_ttl_hours`  | integer   | Container TTL in hours (`0` clears it to `null`; negative is rejected)                              |
| `max_ephemeral_agents` | integer   | Per-crew hire-flow quota; must be between `0` and `100`                                             |
| `network_mode`         | string    | `"free"` or `"restricted"`                                                                          |
| `allowed_domains`      | string\[] | Allowed domains (restricted mode)                                                                   |
| `mcp_config_json`      | string    | Raw MCP server config JSON (must contain `mcpServers` object)                                       |
| `escalation_config`    | string    | Escalation policy config JSON (thresholds 0-1, `auto_approve_threshold` > `require_approval_below`) |
| `issue_prefix`         | string    | Issue identifier prefix (e.g., `"ENG"`); empty string clears                                        |
| `runtime_image`        | string    | Custom runtime image (empty string clears; existence validated)                                     |
| `devcontainer_config`  | string    | Raw devcontainer.json (empty string clears; max 100 KB)                                             |
| `mise_config`          | string    | Raw mise config TOML (empty string clears; max 10 KB)                                               |
| `services_json`        | string    | Sidecar services JSON (empty/whitespace clears; max 64 KB)                                          |

**Response:** `200 OK` -- updated crew object.

<Note>
  Changing `network_mode`, `allowed_domains`, or `services_json` triggers a container restart so the docker provider picks up the new policy / sidecar set on the next agent run. Changing `runtime_image`, `devcontainer_config`, or `mise_config` invalidates `cached_image` + `config_hash` to force a rebuild.
</Note>

| Status | Condition                                                                                                                                                                                                                                                                                                                       |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid field values (name/slug length, slug format, `network_mode`, domain entry, `container_ttl_hours` negative, `max_ephemeral_agents` outside 0-100, MCP JSON missing `mcpServers`, escalation thresholds out of range, oversize or invalid `devcontainer_config`/`mise_config`/`services_json`, `runtime_image` not found) |
| `403`  | Insufficient role                                                                                                                                                                                                                                                                                                               |
| `404`  | Crew not found                                                                                                                                                                                                                                                                                                                  |
| `409`  | Slug already taken                                                                                                                                                                                                                                                                                                              |

***

### Delete Crew

```
DELETE /api/v1/crews/{crewId}?workspace_id={workspaceId}
```

<Warning>
  Soft-deletes the crew (sets `deleted_at`) and cascade-deletes its missions, mission tasks, and crew members so identifier prefixes can be reused. The slug is rewritten to `{slug}_deleted_{id}` on next Create reuse.
</Warning>

**Auth:** Session or CLI token + `OWNER` or `ADMIN` role

**Response:** `200 OK`

```json theme={null}
{ "success": true }
```

| Status | Condition         |
| ------ | ----------------- |
| `403`  | Insufficient role |
| `404`  | Crew not found    |

**WebSocket event:** `crew.deleted` broadcast to `workspace:{workspaceId}`

***

## Members

Manage the per-crew membership roster and optional per-crew role overrides that win over the workspace role for crew-scoped permissions.

### List Members

```
GET /api/v1/crews/{crewId}/members?workspace_id={workspaceId}
```

**Response:** `200 OK` -- array of member objects.

### Add Member

```
POST /api/v1/crews/{crewId}/members?workspace_id={workspaceId}
```

**Auth:** `OWNER`, `ADMIN`, or `MANAGER` role

**Request Body:**

| Field     | Type   | Required | Description                                                                                                                    |
| --------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `user_id` | string | Yes      | Target user's ID (must already be a workspace member)                                                                          |
| `role`    | string | No       | Optional per-crew role override -- one of `OWNER`, `ADMIN`, `MANAGER`, `MEMBER`, `VIEWER`. Omit to inherit the workspace role. |

```json theme={null}
{
  "user_id": "user_abc123",
  "role": "MANAGER"
}
```

**Response:** `201 Created` -- member object with nested `user` (id/email/full\_name/avatar\_url). The `role` override is **not** echoed on the Add response (it is only included by List Members).

| Status | Condition                                                            |
| ------ | -------------------------------------------------------------------- |
| `400`  | Missing `user_id`, invalid `role`, or user is not a workspace member |
| `404`  | Crew not found                                                       |
| `409`  | User is already a member of this crew                                |

### Update Member Role

```
PATCH /api/v1/crews/{crewId}/members/{memberId}?workspace_id={workspaceId}
Content-Type: application/json
```

Elevate or clear the per-crew role override. The crew-member row
carries an optional `role` column that, when set, **wins over** the
workspace-level role for permissions scoped to this crew. Clearing it
(empty or omitted `role`) drops the member back to inheriting their
workspace role.

**Auth:** `OWNER` or `ADMIN` role on the workspace. A per-crew ADMIN
is **not** allowed to reshape membership — that could ladder a MEMBER
straight to OWNER and bypass the workspace gate.

**Request Body:**

| Field  | Type   | Required | Description                                                                                                                       |
| ------ | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `role` | string | No       | One of `OWNER`, `ADMIN`, `MANAGER`, `MEMBER`, `VIEWER`. Omit or pass `""` to clear the override (member inherits workspace role). |

```json theme={null}
{ "role": "ADMIN" }
```

**Response:** `200 OK` — a status envelope echoing the applied role (an empty `role` means the override was cleared):

```json theme={null}
{ "status": "updated", "role": "ADMIN" }
```

| Status | Condition                             |
| ------ | ------------------------------------- |
| `400`  | `role` is not in the enum set         |
| `403`  | Caller is not workspace OWNER / ADMIN |
| `404`  | Crew or member not found              |

### Remove Member

```
DELETE /api/v1/crews/{crewId}/members/{memberId}?workspace_id={workspaceId}
```

**Auth:** `OWNER` or `ADMIN` role

**Response:** `200 OK`

```json theme={null}
{ "success": true }
```

| Status | Condition                |
| ------ | ------------------------ |
| `404`  | Crew or member not found |

***

## Persona

The crew layer of the persona stack — `PERSONA.md` markdown that the
orchestrator injects into every agent in the crew, used as the default
when an agent has no agent-layer file. Resolution is layered: the
agent layer wins if present; otherwise crew default; otherwise the
synthesised "You are the {role}..." stub. See the [Agent Persona
section in the agents reference](/api-reference/agents#agent-persona)
for the cross-layer model and `cap_bytes` (1500).

All three endpoints are operator-only; there is no crew-flavoured
`suggest` analog (agents propose into their own layer, not the crew
default).

### Get Crew Persona

```
GET /api/v1/crews/{crewId}/persona
```

**Response:** `200 OK`

```json theme={null}
{
  "crew_id": "crew-uuid",
  "layer": "crew",
  "content": "# Default persona for this crew...\n",
  "bytes": 412,
  "cap_bytes": 1500
}
```

Unlike [`GET /agents/{id}/persona`](/api-reference/agents#get-persona), this endpoint does **not** report a `from_default` flag — the crew layer either has a file or it doesn't. When empty, `content` is `""` and clients fall through to the per-agent synthesised default.

### Set Crew Persona

```
PUT /api/v1/crews/{crewId}/persona
Content-Type: application/json
```

Writes the crew layer file. Records a row in `memory_versions` keyed
to the crew-layer path so the history endpoint can replay the chain.

**Request:**

```json theme={null}
{ "content": "# Default persona for this crew\n\n..." }
```

**Response:** `200 OK` — `{ "layer": "crew", "bytes": N, "updated": "<rfc3339>" }`

| Status | Condition                            |
| ------ | ------------------------------------ |
| `413`  | `content` exceeds `cap_bytes` (1500) |
| `404`  | Crew not found                       |
| `500`  | Storage write / fsync failure        |

### Reset Crew Persona

```
DELETE /api/v1/crews/{crewId}/persona
```

Removes the crew layer file. Subsequent reads return empty content
and agents fall back to their per-role synthesised default.

**Response:** `204 No Content`

***

## Policy

Each crew carries an autonomy posture that gates every HITL-relevant
decision the orchestrator makes — memory writes, skill creation,
behavior-monitor escalations, persona suggestions, ephemeral spawns.
The dial is read by every PR-B / PR-C subsystem via the shared
`policy.Resolver`. The CLI counterpart is [`crewship policy
get/set`](/cli/policy).

| Field            | Values                                   | Meaning                                                                                                             |
| ---------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `autonomy_level` | `strict` / `guided` / `trusted` / `full` | How much an agent can do without operator approval. `strict` queues everything; `full` auto-applies most decisions. |
| `behavior_mode`  | `warn` / `block`                         | What the behavior monitor does on a flagged action — `warn` lets it through with a journal entry, `block` rejects.  |

### Get Crew Policy

```
GET /api/v1/crews/{crewId}/policy
```

**Response:** `200 OK`

```json theme={null}
{
  "crew_id": "crew-uuid",
  "autonomy_level": "guided",
  "behavior_mode": "warn",
  "set_by_user_id": "user-uuid",
  "set_at": "2026-05-26T14:22:10Z",
  "reason": "Default for newly-onboarded crews"
}
```

`set_by_user_id` / `set_at` / `reason` are omitted on crews whose
policy still matches the seed defaults (no operator has ever flipped
it). On crews that have been touched at least once, the audit triple
records who, when, why.

### Set Crew Policy

```
PUT /api/v1/crews/{crewId}/policy
Content-Type: application/json
```

Replaces the policy. Records the audit triple (`set_by_user_id`, `set_at`,
`reason`) atomically with the value change and invalidates the resolver cache
so the next decision sees the new state.

**Auth:** Session or CLI token + workspace membership (no additional role gate)

**Request:**

```json theme={null}
{
  "autonomy_level": "trusted",
  "behavior_mode": "block",
  "reason": "Crew has cleared 30 days of approvals without an override"
}
```

| Field            | Required    | Notes                                                                                                                                  |
| ---------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `autonomy_level` | yes         | Must be one of the four enum values; other strings return `400`.                                                                       |
| `behavior_mode`  | yes         | `warn` or `block`. Missing field returns `400` (no silent default).                                                                    |
| `reason`         | conditional | Required **only** when `autonomy_level=full` — non-empty, persisted on the audit triple. Optional for `strict` / `guided` / `trusted`. |

**Response:** `200 OK` — same shape as `GET`, reflecting the new
state and the freshly-recorded audit triple.

| Status | Condition                                                                                                      |
| ------ | -------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid enum value, the forbidden `full` + `block` combination, or `reason` missing when `autonomy_level=full` |
| `404`  | Crew not found                                                                                                 |

***

## Avatar

Bulk-apply an avatar style across a crew's agents, or clear per-agent overrides.

### Apply Avatar Style

```
POST /api/v1/crews/{crewId}/apply-avatar-style?workspace_id={workspaceId}
```

Applies an `avatar_style` to all non-deleted agents in the crew, or clears per-agent overrides.

**Auth:** `OWNER` or `ADMIN` role

**Request Body:**

| Field             | Type    | Required    | Description                                                                |
| ----------------- | ------- | ----------- | -------------------------------------------------------------------------- |
| `avatar_style`    | string  | Conditional | Style to apply (required unless `reset_overrides` is `true`)               |
| `reset_overrides` | boolean | No          | When `true`, clears `avatar_style` on every agent (ignores `avatar_style`) |

**Response:** `200 OK`

```json theme={null}
{ "updated": 3, "style": "bottts" }
```

When `reset_overrides` is `true`, the response is `{ "updated": N, "reset": true }`.

| Status | Condition                                            |
| ------ | ---------------------------------------------------- |
| `400`  | `avatar_style` missing and `reset_overrides` not set |
| `404`  | Crew not found                                       |

***

## Connections

Crew connections define which crews can communicate with each other for cross-crew task assignment and messaging.

### List Connections

```
GET /api/v1/crew-connections?workspace_id={workspaceId}
```

**Response:** `200 OK` -- array of connection objects:

```json theme={null}
[
  {
    "id": "cc_abc",
    "workspace_id": "ws_123",
    "from_crew_id": "crew_abc",
    "from_crew_name": "Engineering",
    "from_crew_slug": "engineering",
    "to_crew_id": "crew_xyz",
    "to_crew_name": "Design",
    "to_crew_slug": "design",
    "direction": "bidirectional",
    "status": "active",
    "created_at": "2024-01-15T10:00:00Z",
    "updated_at": "2024-01-15T10:00:00Z"
  }
]
```

### Create Connection

```
POST /api/v1/crew-connections?workspace_id={workspaceId}
```

**Auth:** `OWNER`, `ADMIN`, or `MANAGER` role

**Request Body:**

| Field          | Type   | Required | Default           | Description                                      |
| -------------- | ------ | -------- | ----------------- | ------------------------------------------------ |
| `from_crew_id` | string | Yes      | --                | Source crew ID                                   |
| `to_crew_id`   | string | Yes      | --                | Target crew ID (must differ from `from_crew_id`) |
| `direction`    | string | No       | `"bidirectional"` | `"bidirectional"` or `"unidirectional"`          |

```json theme={null}
{
  "from_crew_id": "crew_abc",
  "to_crew_id": "crew_xyz",
  "direction": "bidirectional"
}
```

**Response:** `201 Created`

```json theme={null}
{ "id": "cc_abc" }
```

| Status | Condition                                                        |
| ------ | ---------------------------------------------------------------- |
| `400`  | Missing IDs, same source/target, or invalid `direction`          |
| `404`  | One or both crews not found in this workspace                    |
| `409`  | Connection already exists (or other UNIQUE/constraint violation) |

### Delete Connection

```
DELETE /api/v1/crew-connections/{connectionId}?workspace_id={workspaceId}
```

**Auth:** `OWNER`, `ADMIN`, or `MANAGER` role

**Response:** `204 No Content`

| Status | Condition            |
| ------ | -------------------- |
| `404`  | Connection not found |

***

## Crew Files (Proxy)

File operations are proxied through the crewshipd sidecar to the crew's container filesystem.

| Endpoint                                             | Method | Description                  |
| ---------------------------------------------------- | ------ | ---------------------------- |
| `GET /api/v1/crews/{crewId}/files`                   | GET    | List files in crew workspace |
| `GET /api/v1/crews/{crewId}/files/download?path=...` | GET    | Download a file              |
| `PUT /api/v1/crews/{crewId}/files/save`              | PUT    | Save/upload a file           |

***

## Crew Assignments

```
GET /api/v1/crews/{crewId}/assignments?workspace_id={workspaceId}
```

List task assignments for a crew.

**Response:** `200 OK` -- array of assignment objects.

***

## Crew Peer Conversations

```
GET /api/v1/crews/{crewId}/peer-conversations?workspace_id={workspaceId}
```

List peer-to-peer conversations between agents in a crew.

***

## Crew Standup

```
GET /api/v1/crews/{crewId}/standup?workspace_id={workspaceId}
```

Get standup summary for a crew.

***

## Crew Escalations

### List Escalations

```
GET /api/v1/crews/{crewId}/escalations?workspace_id={workspaceId}
```

### Resolve Escalation

```
PATCH /api/v1/escalations/{escalationId}/resolve?workspace_id={workspaceId}
```

### Pending Escalation Count

```
GET /api/v1/escalations/pending-count?workspace_id={workspaceId}
```

Returns count of pending escalations across the workspace.
