---
title: Space
kind: resource
slug: space
summary: "A named container of Droplets with its own membership and join policy. The unit of team-level scope on Drop."
---

# Space

A named container of Droplets with its own membership and join policy. The unit of team-level scope on Drop.

A **Space** groups related [Droplets](/docs/resources/droplet) and answers the question "who is this group of work for?" Each Space has an owner, a roster of members with roles (OWNER / EDITOR / READ_ONLY, default EDITOR when added or joined), and a `joinPolicy` that controls whether outsiders can self-join. Droplets in a Space can use `internalAccess: SPACE` to bind their audience live to the current member list — add a member, they immediately see every `SPACE`-mode Droplet.

**Asking to join an invite-only Space.** `INVITE_ONLY` Spaces can't be self-joined, but anyone at Hi Marley can ask: `request-access` records the ask (with an optional note) and notifies every member with the OWNER role — in Drop, by email, and by Slack DM with Add / Dismiss buttons, per each owner's **Access** notification setting. Owners review with `requests list` / `grant` (EDITOR by default, or READ_ONLY) / `dismiss`, or from Space settings → Members. The requester is told when they're added; a dismissal is silent, and they can ask again after 24 hours. Someone added through any other path (the member picker, a User Group, auto-member) has their pending request closed automatically.

## The Space object

### Attributes

- **`slug`** `string` — URL-friendly identifier. Stable for the lifetime of the Space — appears in `/spaces/{slug}` and every API path. Lowercase alphanumeric + hyphens, min 2 characters, globally unique.
- **`name`** `string` — Human-readable display name. Unique across all Spaces.
- **`description`** `string · nullable` — Optional prose blurb shown on the Space landing page and in the Space directory.
- **`joinPolicy`** `enum` (enum: `INVITE_ONLY` / `ANYONE_HIMARLEY` / `EVERYONE_AUTOMEMBER`) — **Who can join.** `INVITE_ONLY` — members are added explicitly; non-members see the row but cannot self-join (`POST /join` returns `403 CANNOT_SELF_JOIN`). `ANYONE_HIMARLEY` — any @himarley.com user can self-join with one click. `EVERYONE_AUTOMEMBER` (admin-only) — every @himarley user is added at signup; an enable triggers a one-time backfill.
- **`status`** `enum` (enum: `ACTIVE` / `ARCHIVED`) — Hard-status. Distinct from `archivedAt` — `ARCHIVED` here means the Space is tombstoned; the lifecycle archive uses `archivedAt`.
- **`archivedAt`** `timestamp · nullable` — Non-destructive archive timestamp. When set, the Space is hidden from `/spaces` and from search by default; member Droplets cascade-archive too (029-freshness). Restore with [Unarchive](#unarchive).
- **`featuredTags`** `string[]` — Admin-curated tag chips shown in the Space header. Clicking a chip narrows the Space landing to Droplets carrying that tag (`tags @>`). Lowercase-normalized, deduped, soft cap of 12; order preserved.
- **`icon`** `string · nullable` — Lucide React icon name (e.g. `rocket`, `users`, `briefcase`). Rendered in the sidebar + Space header.
- **`linkedGroupId`** `string · nullable` — Optional pointer to a paired [User Group](/docs/resources/user-group) (e.g. the `engineering` Space links to the `@engineering` Group). Pure cross-reference — there is no membership sync between the two.
- **`id`** `string · less-common` — CUID primary key. Use `slug` in API paths and URLs; the `id` shows up in audit events and the `toSpaceId` argument of [`POST /move`](/docs/resources/droplet#move).
- **`createdById`** `string · less-common` — The User who created the Space. Distinct from current owner — the Space owner role lives on the `SpaceMember` join row.
- **`slackChannelId`** `string · less-common` — If the Space is linked to a Slack channel for notifications, the channel id. Set during the (planned) Slack channel-binding flow.
- **`slackChannelName`** `string · less-common` — Cached channel name for display alongside `slackChannelId`. Re-synced lazily.
- **`privateTagPrefixes`** `string[] · less-common` — Tag prefixes (e.g. `hr/`, `legal/`) whose tagged Droplets render tags only to members. See feature 024-tags-ux. Empty by default.
- **`createdAt`** `timestamp · less-common` — ISO 8601 timestamp of creation.
- **`updatedAt`** `timestamp · less-common` — ISO 8601 timestamp of last metadata update.

### JSON example

```json
{
  "id": "spc_platform_team",
  "slug": "platform-team",
  "name": "Platform Team",
  "description": "Roadmaps, retros, and decision logs for the platform team",
  "icon": "rocket",
  "joinPolicy": "INVITE_ONLY",
  "status": "ACTIVE",
  "featuredTags": ["roadmap", "retro"],
  "privateTagPrefixes": [],
  "slackChannelId": null,
  "slackChannelName": null,
  "linkedGroupId": null,
  "archivedAt": null,
  "createdById": "usr_alex",
  "createdAt": "2026-04-20T10:00:00.000Z",
  "updatedAt": "2026-05-31T09:00:00.000Z"
}
```

## Methods

### List Spaces

`GET /api/spaces`

Return Spaces the caller belongs to. Pass `?include=all` to browse the directory — every Space the caller can see (including `INVITE_ONLY`, with the member roster suppressed). Each row carries `joinPolicy`, `memberCount`, `appCount`, `ownerEmail`/`ownerName`, and a computed `canSelfJoin` flag.

**Parameters**

- **`include`** _(string · query)_ — `all` to browse every Space (directory mode). Default: only Spaces the caller belongs to.
- **`includeArchived`** _(boolean · query)_ — Include archived Spaces (default false).

**CLI**

```bash
drop spaces list
```

**MCP**

```json
{
  "tool": "drop_spaces",
  "arguments": {}
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/spaces' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
[
  {
    "id": "spc_platform_team",
    "name": "Platform Team",
    "slug": "platform-team",
    "description": "Roadmaps, retros, and decision logs",
    "icon": "rocket",
    "joinPolicy": "INVITE_ONLY",
    "internalAccess": "PRIVATE",
    "status": "ACTIVE",
    "memberCount": 12,
    "appCount": 8,
    "isMember": true,
    "canSelfJoin": false,
    "role": "OWNER",
    "createdAt": "2026-04-20T10:00:00.000Z",
    "createdBy": {
      "name": "Alex Chen",
      "email": "alex@himarley.com"
    }
  }
]
```

### Create a Space

`POST /api/spaces`

Create a new Space. The caller becomes the owner. Slug rules match Droplets (lowercase alphanumeric + hyphens, min 2 chars, globally unique). `joinPolicy` sets the membership policy at create time. Setting `joinPolicy: EVERYONE_AUTOMEMBER` requires admin privilege and triggers a backfill of every @himarley user as a MEMBER. If the slug collides with an existing User Group slug, the response includes a non-blocking `collisionWarning` for the UI to suggest linking.

**Parameters**

- **`name`** _(string · body · required)_ — Display name for the Space (1-100 characters).
- **`slug`** _(string · body)_ — URL slug (auto-generated from name if omitted).
- **`description`** _(string · body)_ — Brief description.
- **`icon`** _(string · body)_ — Lucide React icon name.
- **`joinPolicy`** _(enum · body)_ — INVITE_ONLY | ANYONE_HIMARLEY | EVERYONE_AUTOMEMBER. Default INVITE_ONLY.

**CLI**

```bash
drop spaces create --name "Platform Team" --slug platform-team --description "Roadmaps, retros, and decision logs" --join-policy ANYONE_HIMARLEY
```

**MCP**

```json
{
  "tool": "drop_create_space",
  "arguments": {
    "name": "Platform Team",
    "slug": "platform-team",
    "description": "Roadmaps, retros, and decision logs",
    "joinPolicy": "ANYONE_HIMARLEY"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Platform Team","slug":"platform-team","description":"Roadmaps, retros, and decision logs","joinPolicy":"ANYONE_HIMARLEY"}'
```

**Response** (201)

```json
{
  "space": {
    "id": "spc_platform_team",
    "name": "Platform Team",
    "slug": "platform-team",
    "description": "Roadmaps, retros, and decision logs",
    "icon": null,
    "joinPolicy": "ANYONE_HIMARLEY",
    "internalAccess": "EVERYONE",
    "memberCount": 1,
    "appCount": 0,
    "isMember": true,
    "role": "OWNER",
    "createdAt": "2026-04-20T10:00:00.000Z",
    "createdBy": {
      "name": "Alex Chen",
      "email": "alex@himarley.com"
    }
  },
  "collisionWarning": null
}
```

### Retrieve a Space

`GET /api/spaces/{slug}`

Return full Space detail by slug — metadata, join policy, owner, current member roster. Members see the full roster; non-members of `INVITE_ONLY` Spaces get a 404 to hide existence. Open Spaces (`ANYONE_HIMARLEY` / `EVERYONE_AUTOMEMBER`) are visible to every @himarley user.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces get platform-team
```

**MCP**

```json
{
  "tool": "drop_get_space",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/spaces/platform-team' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "id": "spc_platform_team",
  "name": "Platform Team",
  "slug": "platform-team",
  "description": "Roadmaps, retros, and decision logs for the platform team",
  "icon": "rocket",
  "joinPolicy": "INVITE_ONLY",
  "internalAccess": "PRIVATE",
  "status": "ACTIVE",
  "slackChannelId": null,
  "slackChannelName": null,
  "featuredTags": [
    "roadmap",
    "retro"
  ],
  "linkedGroup": null,
  "memberCount": 12,
  "appCount": 8,
  "isMember": true,
  "role": "OWNER",
  "createdAt": "2026-04-20T10:00:00.000Z",
  "createdBy": {
    "name": "Alex Chen",
    "email": "alex@himarley.com"
  },
  "members": [
    {
      "id": "spm_alex",
      "userId": "usr_alex",
      "name": "Alex Chen",
      "email": "alex@himarley.com",
      "role": "OWNER",
      "createdAt": "2026-04-20T10:00:00.000Z"
    }
  ]
}
```

### Update a Space

`PATCH /api/spaces/{slug}`

Patch Space metadata — `name`, `description`, `icon`, `joinPolicy`, `featuredTags`, `linkedGroupId`. Owner-only for most fields; OWNER or EDITOR can patch `featuredTags` (curator-level). Setting `joinPolicy: EVERYONE_AUTOMEMBER` is admin-gated and triggers a member backfill. Reverting from auto-member never silently revokes members. The legacy `internalAccess` field is accepted as an alias by the server but `SPECIFIC` is rejected with `400 INVALID_POLICY`.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.
- **`name`** _(string · body)_ — New display name (1-100 chars).
- **`description`** _(string | null · body)_ — New description; null to clear.
- **`icon`** _(string | null · body)_ — Lucide React icon; null to clear.
- **`joinPolicy`** _(enum · body)_ — INVITE_ONLY | ANYONE_HIMARLEY | EVERYONE_AUTOMEMBER (admin-only).
- **`status`** _(enum · body)_ — ACTIVE | ARCHIVED.
- **`featuredTags`** _(string[] · body)_ — Admin-curated tag chips (max 5, lowercase-normalized).
- **`linkedGroupId`** _(string | null · body)_ — Cross-reference User Group id, or null to clear.

**CLI**

```bash
drop spaces update platform-team --description "Engineering platform team — owns CI, infra, and Drop." --featured-tags dashboard,retro
```

**MCP**

```json
{
  "tool": "drop_update_space",
  "arguments": {
    "slug": "platform-team",
    "description": "Engineering platform team — owns CI, infra, and Drop.",
    "featuredTags": [
      "dashboard",
      "retro"
    ]
  }
}
```

**HTTP**

```bash
curl -X PATCH 'https://drop.himarley.ai/api/spaces/platform-team' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"description":"Engineering platform team — owns CI, infra, and Drop.","featuredTags":["dashboard","retro"]}'
```

**Response** (200)

```json
{
  "id": "spc_platform_team",
  "name": "Platform Team",
  "slug": "platform-team",
  "description": "Engineering platform team — owns CI, infra, and Drop.",
  "icon": "rocket",
  "joinPolicy": "INVITE_ONLY",
  "internalAccess": "PRIVATE",
  "status": "ACTIVE",
  "featuredTags": [
    "dashboard",
    "retro"
  ],
  "linkedGroup": null,
  "memberCount": 12,
  "appCount": 8,
  "isMember": true,
  "role": "OWNER",
  "createdAt": "2026-04-20T10:00:00.000Z",
  "createdBy": {
    "name": "Alex Chen",
    "email": "alex@himarley.com"
  }
}
```

### Delete a Space

`DELETE /api/spaces/{slug}`

Irreversible hard-delete (029-freshness FR-031). Gated by the clean-slate rule: caller must be the sole OWNER, the Space must have zero member Droplets, and zero non-owner `SpaceMember` rows. On gate failure returns `409 NOT_CLEAN_SLATE` with `{ code, reason, message }`. Otherwise removes the Space row (SpaceMember cascade-deletes).

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces delete old-experimental-space
```

**MCP**

```json
{
  "tool": "drop_delete_space",
  "arguments": {
    "slug": "old-experimental-space"
  }
}
```

**HTTP**

```bash
curl -X DELETE 'https://drop.himarley.ai/api/spaces/old-experimental-space' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "success": true,
  "hardDeleted": true
}
```

### List members

`GET /api/spaces/{slug}/members`

Return every member of the Space with role + join timestamp. Visible to members; for `INVITE_ONLY` Spaces non-members get 403.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces members list platform-team
```

**MCP**

```json
{
  "tool": "drop_space_members",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/spaces/platform-team/members' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
[
  {
    "id": "spm_alex",
    "userId": "usr_alex",
    "name": "Alex Chen",
    "email": "alex@himarley.com",
    "role": "OWNER",
    "createdAt": "2026-04-20T10:00:00.000Z"
  }
]
```

### Add member(s)

`POST /api/spaces/{slug}/members`

Add one or more people to a Space by email. Owner-only. Idempotent — already-members are returned in `alreadyMembers`; unknown emails are skipped silently. New members immediately inherit access to every `SPACE`-mode Droplet in the Space (edge cache resynced fire-and-forget). New members are added as `EDITOR` by default; pass `READ_ONLY` for view-only access. `OWNER` is not accepted via this endpoint — use `update-member` to promote.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.
- **`emails`** _(string[] · body · required)_ — 1–100 emails to add.
- **`role`** _(enum · body)_ — EDITOR (default) or READ_ONLY.

**CLI**

```bash
drop spaces members add platform-team --emails kai@himarley.com,jane@himarley.com
```

**MCP**

```json
{
  "tool": "drop_add_space_members",
  "arguments": {
    "slug": "platform-team",
    "emails": [
      "kai@himarley.com",
      "jane@himarley.com"
    ]
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/platform-team/members' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"emails":["kai@himarley.com","jane@himarley.com"]}'
```

**Response** (200)

```json
{
  "added": [
    {
      "email": "kai@himarley.com",
      "role": "EDITOR"
    },
    {
      "email": "jane@himarley.com",
      "role": "EDITOR"
    }
  ],
  "alreadyMembers": []
}
```

### Change a member's role

`PATCH /api/spaces/{slug}/members/{userId}`

Owner-only. Promote or demote a Space member. Promoting to OWNER does not transfer ownership exclusively — multiple OWNERs are allowed. Demoting the last OWNER is blocked via the `leave` / `remove-member` last-owner guard.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.
- **`userId`** _(string · path · required)_ — The user id whose role to change.
- **`role`** _(enum · body · required)_ — OWNER | EDITOR | READ_ONLY.

**CLI**

```bash
drop spaces members set-role platform-team usr_kai --role EDITOR
```

**MCP**

```json
{
  "tool": "drop_update_space_member",
  "arguments": {
    "slug": "platform-team",
    "userId": "usr_kai",
    "role": "EDITOR"
  }
}
```

**HTTP**

```bash
curl -X PATCH 'https://drop.himarley.ai/api/spaces/platform-team/members/usr_kai' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"role":"EDITOR"}'
```

**Response** (200)

```json
{
  "success": true,
  "role": "EDITOR"
}
```

### Remove member

`DELETE /api/spaces/{slug}/members/{userId}`

Remove a single member. OWNER can remove anyone; members can only remove themselves (self-removal == leave). Cannot remove the last OWNER — `400` returned.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.
- **`userId`** _(string · path · required)_ — The user id to remove.

**CLI**

```bash
drop spaces members remove platform-team usr_kai
```

**MCP**

```json
{
  "tool": "drop_remove_space_member",
  "arguments": {
    "slug": "platform-team",
    "userId": "usr_kai"
  }
}
```

**HTTP**

```bash
curl -X DELETE 'https://drop.himarley.ai/api/spaces/platform-team/members/usr_kai' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "success": true
}
```

### Self-join a Space

`POST /api/spaces/{slug}/join`

Self-join a Space. Returns 201 with the new MEMBER row for `ANYONE_HIMARLEY`. For `EVERYONE_AUTOMEMBER` users are added at signup; calling join when already a member returns `409` (the client treats it as idempotent success). For `INVITE_ONLY` returns `403 CANNOT_SELF_JOIN` with `ownerEmail` / `ownerName` so the client can render "ask the owner for an invite".

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces join platform-team
```

**MCP**

```json
{
  "tool": "drop_join_space",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/platform-team/join' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (201)

```json
{
  "membership": {
    "id": "spm_kai",
    "spaceId": "spc_platform_team",
    "userId": "usr_kai",
    "role": "EDITOR",
    "createdAt": "2026-05-31T09:15:00.000Z"
  }
}
```

### Leave a Space

`POST /api/spaces/{slug}/leave`

Leave a Space. Your Droplets in the Space stay in the Space. Cannot leave as the last OWNER — transfer ownership first (`400 LAST_OWNER`).

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces leave platform-team
```

**MCP**

```json
{
  "tool": "drop_leave_space",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/platform-team/leave' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "success": true
}
```

### Get audit log

`GET /api/spaces/{slug}/audit`

Return the Space-scoped audit log — membership adds/removes, role changes, join-policy changes, automember toggles, bulk backfills, featured-tag changes, and Droplet moves into / between this Space. Caller must be a MEMBER; non-members get 404 to avoid leaking existence. Cursor-paginated, newest-first.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.
- **`cursor`** _(string · query)_ — Audit-event id from previous response's nextCursor.
- **`limit`** _(number · query)_ — Page size (default 50, max 100).
- **`event`** _(string · query)_ — Optional event-name prefix filter.

**CLI**

```bash
drop spaces audit platform-team
```

**MCP**

```json
{
  "tool": "drop_space_audit",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/spaces/platform-team/audit' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "events": [
    {
      "id": "ae_1",
      "event": "space.member_added",
      "actorEmail": "alex@himarley.com",
      "actorName": "Alex Chen",
      "details": {
        "memberEmail": "kai@himarley.com",
        "role": "EDITOR"
      },
      "ipAddress": null,
      "createdAt": "2026-05-31T09:15:00.000Z"
    }
  ],
  "nextCursor": null,
  "hasMore": false,
  "pagination": {
    "limit": 50,
    "total": 1
  }
}
```

### Archive

`POST /api/spaces/{slug}/archive`

Non-destructive archive. Sets `archivedAt`, hides the Space from default lists, **and cascade-archives every Droplet in the Space** (029-freshness). Returns the count of cascaded Droplets + notified owners + a `correlationId` shared by all the cascaded audit events. Owner-only. Idempotent — calling on an already-archived Space returns 204.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces archive platform-team
```

**MCP**

```json
{
  "tool": "drop_archive_space",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/platform-team/archive' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "archivedAt": "2026-05-31T09:15:00.000Z",
  "cascadedDropletCount": 7,
  "notifiedOwnerCount": 3,
  "correlationId": "space-archive-clx7"
}
```

### Preview archive cascade

`GET /api/spaces/{slug}/archive/preview`

Return the blast radius of archiving the Space — total Droplet count, how many are yours, how many belong to other owners, plus up to 5 of those owners' emails. Visible to anyone who can GET the Space (non-members of `INVITE_ONLY` Spaces get 404).

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces archive-preview platform-team
```

**MCP**

```json
{
  "tool": "drop_archive_space_preview",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/spaces/platform-team/archive/preview' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "totalDropletCount": 7,
  "ownedDropletCount": 5,
  "otherDropletCount": 2,
  "affectedOwnerCount": 2,
  "otherOwnerEmails": [
    "kai@himarley.com",
    "jane@himarley.com"
  ]
}
```

### Unarchive

`POST /api/spaces/{slug}/unarchive`

Clear `archivedAt` and return the Space to the active directory. Cascaded Droplets stay archived — owners restore them individually with `drop unarchive`. Owner-only. Idempotent — calling on an active Space returns 204.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces unarchive platform-team
```

**MCP**

```json
{
  "tool": "drop_unarchive_space",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/platform-team/unarchive' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "slug": "platform-team",
  "archivedAt": null
}
```

### Bootstrap a User Group

`POST /api/spaces/{slug}/bootstrap-group`

Convenience: create a new [User Group](/docs/resources/user-group) seeded from this Space's current roster, then set `linkedSpaceId` / `linkedGroupId` on both sides so the cross-reference is in place. **People Admin only.** No ongoing membership sync — the seed is one-shot.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces bootstrap-group platform-team
```

**MCP**

```json
{
  "tool": "drop_bootstrap_group_from_space",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/platform-team/bootstrap-group' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (201)

```json
{
  "groupSlug": "platform-team",
  "memberCount": 12
}
```

### Add a User Group's roster

`POST /api/spaces/{slug}/members/from-group`

Copy a User Group's current roster into an existing Space. One-time copy — future changes to the Group's roster do NOT propagate. Authorization: Space owner OR People Admin OR site admin.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.
- **`groupSlug`** _(string · body · required)_ — The User Group slug to copy from.

**CLI**

```bash
drop spaces members add-from-group platform-team --group-slug engineering
```

**MCP**

```json
{
  "tool": "drop_space_add_members_from_group",
  "arguments": {
    "slug": "platform-team",
    "groupSlug": "engineering"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/platform-team/members/from-group' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"groupSlug":"engineering"}'
```

**Response** (200)

```json
{
  "added": 8,
  "alreadyMembers": 2,
  "totalGroupMembers": 10
}
```

### Mark a Space visit

`POST /api/spaces/{slug}/visits`

Internal endpoint — upsert the viewer's `UserSpaceVisit` row with `lastVisitAt = now()` for this Space and return the PREVIOUS value (null on first visit). The Space Agent panel POSTs this on open; the returned timestamp becomes the `since` cursor on the immediately-following `GET /api/changes?subject=space:S&since=…` so the "what's new since I was here" pane renders in one round-trip. Caller must be a Space member (non-members get 404 to hide existence on INVITE_ONLY).

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces _visit platform-team
```

**MCP**

```json
{
  "tool": "drop_mark_space_visit",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/platform-team/visits' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "lastVisitAt": "2026-06-07T09:15:00.000Z"
}
```

### Star a Space

`POST /api/spaces/{slug}/star`

Star a Space to pin it to the top of your sidebar. Personal to you — other members are unaffected. Idempotent: starring an already-starred Space returns the existing `starredAt`. Caller must be a member (`404 SPACE_NOT_FOUND` otherwise).

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces star platform-team
```

**MCP**

```json
{
  "tool": "drop_star_space",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/platform-team/star' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "success": true,
  "starredAt": "2026-06-19T09:15:00.000Z"
}
```

### Unstar a Space

`DELETE /api/spaces/{slug}/star`

Remove your star from a Space. Personal to you. Idempotent: unstarring a Space that is not starred returns success with `starredAt: null`. Caller must be a member (`404 SPACE_NOT_FOUND` otherwise).

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces unstar platform-team
```

**MCP**

```json
{
  "tool": "drop_unstar_space",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X DELETE 'https://drop.himarley.ai/api/spaces/platform-team/star' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "success": true,
  "starredAt": null
}
```

### Hide a Space

`POST /api/spaces/{slug}/mute`

Hide a Space so it drops out of your active sidebar (it moves to Inactive). Personal to you — other members are unaffected; the Space is NOT archived, and your notifications are unchanged. Idempotent. Caller must be a member (`404 SPACE_NOT_FOUND` otherwise).

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces hide platform-team
```

**MCP**

```json
{
  "tool": "drop_mute_space",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/platform-team/mute' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "success": true,
  "mutedAt": "2026-06-19T09:15:00.000Z"
}
```

### Unhide a Space

`DELETE /api/spaces/{slug}/mute`

Unhide a Space so it returns to its relationship group in your sidebar. Personal to you. Idempotent: unhiding a Space that is not hidden returns success with `mutedAt: null`. Caller must be a member (`404 SPACE_NOT_FOUND` otherwise).

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces unhide platform-team
```

**MCP**

```json
{
  "tool": "drop_unmute_space",
  "arguments": {
    "slug": "platform-team"
  }
}
```

**HTTP**

```bash
curl -X DELETE 'https://drop.himarley.ai/api/spaces/platform-team/mute' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "success": true,
  "mutedAt": null
}
```

### Request access

`POST /api/spaces/{slug}/access-requests`

Ask to join an invite-only Space. Every Space owner is notified on the channels they have on for "Access" (bell, email, Slack DM). Repeat requests are de-duplicated — the response reports the existing request (`outcome: EXISTING`) and nobody is notified again. A request an owner turned down also reads as `EXISTING` for 24 hours (dismissals are never announced); after that, asking again re-opens it. Errors: `409 ALREADY_MEMBER`, `409 OPEN_SPACE` (join it instead), `404 SPACE_NOT_FOUND` (missing or archived).

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.
- **`message`** _(string · body)_ — Optional note for the owners (≤500 chars).

**CLI**

```bash
drop spaces request-access brand-hub --message "Picking up the logo audit from Mia this sprint."
```

**MCP**

```json
{
  "tool": "drop_request_space_access",
  "arguments": {
    "slug": "brand-hub",
    "message": "Picking up the logo audit from Mia this sprint."
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/brand-hub/access-requests' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message":"Picking up the logo audit from Mia this sprint."}'
```

**Response** (201)

```json
{
  "request": {
    "id": "sar_123",
    "status": "PENDING",
    "createdAt": "2026-10-15T14:12:00.000Z"
  },
  "outcome": "CREATED"
}
```

### List access requests

`GET /api/spaces/{slug}/access-requests`

Owner-only. Pending requests to join the Space, most recent first.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.

**CLI**

```bash
drop spaces requests list brand-hub
```

**MCP**

```json
{
  "tool": "drop_list_space_access_requests",
  "arguments": {
    "slug": "brand-hub"
  }
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/spaces/brand-hub/access-requests' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
[
  {
    "id": "sar_123",
    "message": "Picking up the logo audit from Mia this sprint.",
    "createdAt": "2026-10-15T14:12:00.000Z",
    "requester": {
      "id": "usr_sam",
      "name": "Sam Chen",
      "email": "sam@himarley.com"
    }
  }
]
```

### Grant an access request

`POST /api/spaces/{slug}/access-requests/{requestId}/grant`

Owner-only. Adds the requester to the Space (EDITOR by default, or READ_ONLY) and tells them they're in. If they already joined another way their existing role is kept. Idempotent: a request another owner already handled returns `outcome: ALREADY_RESOLVED` with its current status.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.
- **`requestId`** _(string · path · required)_ — The request id (from `requests list`).
- **`role`** _(enum · body)_ — EDITOR (default) or READ_ONLY.

**CLI**

```bash
drop spaces requests grant brand-hub sar_123 --role EDITOR
```

**MCP**

```json
{
  "tool": "drop_grant_space_access_request",
  "arguments": {
    "slug": "brand-hub",
    "requestId": "sar_123",
    "role": "EDITOR"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/brand-hub/access-requests/sar_123/grant' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"role":"EDITOR"}'
```

**Response** (200)

```json
{
  "outcome": "GRANTED",
  "status": "GRANTED",
  "role": "EDITOR"
}
```

### Dismiss an access request

`POST /api/spaces/{slug}/access-requests/{requestId}/dismiss`

Owner-only. Turns the request down without adding the requester. They aren't told; they can ask again after 24 hours. Idempotent like grant.

**Parameters**

- **`slug`** _(string · path · required)_ — The Space slug.
- **`requestId`** _(string · path · required)_ — The request id (from `requests list`).

**CLI**

```bash
drop spaces requests dismiss brand-hub sar_123
```

**MCP**

```json
{
  "tool": "drop_dismiss_space_access_request",
  "arguments": {
    "slug": "brand-hub",
    "requestId": "sar_123"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/spaces/brand-hub/access-requests/sar_123/dismiss' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "outcome": "DISMISSED",
  "status": "DISMISSED"
}
```
