# Projects

> The Projects operations of the Kabu API: repos, services or environments; the unit of key binding and retention.

Repos, services or environments; the unit of key binding and retention.

Generated from the contract, version `2.0.0-draft.2` ([OpenAPI](/openapi.yaml)). Base URL `https://usekabu.com/api`. Errors are problem details, each [code](/docs/errors) with its own page.

## `GET /v2/projects` · List projects

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `messages:read`.
- **Signed-in humans:** `member`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `limit` | query | integer, from 1 to 100 | no |  |
| `after` | query | string | no | Opaque cursor from next_cursor. |
| `include_counts` | query | boolean | no | Give each project its `message_count`, the messages projects.delete would remove. Off by default: it costs a count. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

| Status | Means | Body |
|---|---|---|
| `200` | OK | [ProjectList](#schema-ProjectList) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl "https://usekabu.com/api/v2/projects" \
  -H "Authorization: Bearer $KABU_API_KEY"
```

## `POST /v2/projects` · Create a project

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `projects:write`.
- **Signed-in humans:** `admin`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `Idempotency-Key` | header | string, at most 255 characters | no | Retry-safe key. For messages, sessions and responses it binds to the created object for its life; elsewhere for 24 h. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

**Request body**: [ProjectWrite](#schema-ProjectWrite).

| Status | Means | Body |
|---|---|---|
| `201` | OK | [Project](#schema-Project) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X POST "https://usekabu.com/api/v2/projects" \
  -H "Authorization: Bearer $KABU_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
```

## `GET /v2/projects/{project}` · Get a project

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `messages:read`.
- **Signed-in humans:** `member`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `project` | path | string | yes | An id or slug. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

| Status | Means | Body |
|---|---|---|
| `200` | OK | [Project](#schema-Project) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl "https://usekabu.com/api/v2/projects/$PROJECT" \
  -H "Authorization: Bearer $KABU_API_KEY"
```

## `PATCH /v2/projects/{project}` · Update a project

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `projects:write`.
- **Signed-in humans:** `admin`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `project` | path | string | yes | An id or slug. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

**Request body**: [ProjectWrite](#schema-ProjectWrite).

| Status | Means | Body |
|---|---|---|
| `200` | OK | [Project](#schema-Project) |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X PATCH "https://usekabu.com/api/v2/projects/$PROJECT" \
  -H "Authorization: Bearer $KABU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

## `DELETE /v2/projects/{project}` · Delete a project and everything in it

Deletes the project and all that is only its: its messages (with their content, answers and delivery records), sessions, aliases, agents, webhook endpoints and steering claims, the media stored in it, and any project merged into it. It cannot be undone. Its pending asks are cancelled first, as any cancel, so the org's open asks, their sessions and each person's backlog are freed. Its stored bytes are deleted shortly after, retried until they are gone, and its Live Activities are ended. An attachment uploaded with an unbound key is stored in `default`, where any message may share it, so it stays until `default`'s retention purges it. Nothing remembers the slug: the next message that names it creates a fresh project, as any first use does, and a message naming one of its sessions is accepted without the session. Other projects' deliveries are untouched. Only the project's `prj_…` id or its exact slug is accepted; an alias or a normalised match answers 404, so a merged slug never deletes its merge target. While an API key that is neither revoked nor expired is bound to the project or one of its agents, it answers 409 project_in_use naming the keys: revoke them first. The project `default` resolves to, which takes every message that names no project, cannot be deleted (400 invalid_request). A project that no longer exists answers 404, so repeating a delete is harmless.

- **Auth:** an API key, or a signed-in human's token (the Kabu apps).
- **Scopes:** `projects:write`.
- **Signed-in humans:** `admin`.

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `project` | path | string | yes | An id or slug. |
| `Kabu-Org` | header | string | no | Select a live membership (default personal org); non-member is 404. Human tokens are live-only. Caller user id is distinct from billing owner. |

| Status | Means | Body |
|---|---|---|
| `204` | Done |  |
| `4XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |
| `5XX` | Error | [Problem](/docs/api/discovery#schema-Problem) |

```sh
curl -X DELETE "https://usekabu.com/api/v2/projects/$PROJECT" \
  -H "Authorization: Bearer $KABU_API_KEY"
```

## Schemas

### ProjectList

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"list"` | yes |  |
| `data` | array of [Project](#schema-Project) | yes |  |
| `has_more` | boolean | yes |  |
| `next_cursor` | string or null | yes |  |

### Project

| Property | Type | Required | Description |
|---|---|---|---|
| `object` | `"project"` | yes |  |
| `id` | string | yes |  |
| `slug` | string | yes |  |
| `name` | string or null | yes |  |
| `normalized_key` | string | no |  |
| `aliases` | array of string | no |  |
| `color` | [Accent](/docs/api/messages#schema-Accent) | no | Open enum. Known values are `AccentKnown`; accept and ignore unknown values. |
| `icon` | string | no | A `med_…` avatar media id (or SF Symbol fallback when not `med_…`). |
| `symbol` | string | no | An avatar glyph: `sf:NAME`, `emoji:CHAR` or `art:ID` (a bare SF Symbol name is also read). |
| `retention_days` | integer | yes |  |
| `settings` | [SettingsView](/docs/api/events#schema-SettingsView) | yes | Layered: org → project → agent → recipient. Unset fields inherit. |
| `metadata` | [Metadata](/docs/api/messages#schema-Metadata) | yes | Your own key/value pairs (≤ 50 keys, values ≤ 500 chars), returned verbatim on every object and event. |
| `created_at` | [Timestamp](/docs/api/messages#schema-Timestamp) | yes |  |
| `message_count` | integer, ≥ 0 | no | How many messages the project holds, all of which projects.delete removes. Only with projects.list `include_counts=true`. |

### ProjectWrite

| Property | Type | Required | Description |
|---|---|---|---|
| `slug` | string, matching `^[a-z0-9][a-z0-9._-]{0,63}$` | no |  |
| `name` | string, at most 80 characters | no |  |
| `color` | [AccentKnown](/docs/api/messages#schema-AccentKnown) | no | A palette token (rendered with each platform's accessible colours), never a raw colour. |
| `icon` | string, at most 64 characters | no | A `med_…` avatar media id (or SF Symbol fallback when not `med_…`). |
| `symbol` | string, at most 64 characters | no | An avatar glyph: `sf:NAME`, `emoji:CHAR` or `art:ID` (a bare SF Symbol name is also read). |
| `merge_into` | string, 1 to 64 characters | no | Deprecated; will be removed in a later build. Delete the project instead (projects.delete). Target project id, slug or normalised key to merge this project into as an alias: its messages move to the target, and every later message that names this project's slug lands in the target too. It cannot be undone. **Deprecated.** |
| `retention_days` | integer, from 1 to 365 | no |  |
| `settings` | [Settings](#schema-Settings) | no | Layered: org → project → agent → recipient. Unset fields inherit. |
| `metadata` | [Metadata](/docs/api/messages#schema-Metadata) | no | Your own key/value pairs (≤ 50 keys, values ≤ 500 chars), returned verbatim on every object and event. |

Unknown properties are refused.

### Settings

Layered: org → project → agent → recipient. Unset fields inherit.

| Property | Type | Required | Description |
|---|---|---|---|
| `appearance` | [Appearance](/docs/api/messages#schema-Appearance) | no | Cosmetics only. Resolved org → project → agent → recipient, then this override; recipient restrictions always win. |
| `delivery` | [DeliverySettings](#schema-DeliverySettings) | no |  |
| `extensions` | [Extensions](/docs/api/messages#schema-Extensions) | no | Reverse-domain namespaced extension data (e.g. `com.example.ticket`), ≤ 8 KiB total. Cannot change core semantics. |

Unknown properties are refused.

### DeliverySettings

| Property | Type | Required | Description |
|---|---|---|---|
| `quiet_hours` | object | no |  |
| `quiet_hours.start` | string, matching `^[0-2][0-9]:[0-5][0-9]$` | no |  |
| `quiet_hours.end` | string, matching `^[0-2][0-9]:[0-5][0-9]$` | no |  |
| `quiet_hours.time_zone` | string | no |  |
| `digest_notify_after` | integer, from 1 to 200 | no | Notifies per 10 min before FYIs are digested. |
| `remind_every` | integer, from 0 to 86400 | no | Seconds between reminders of a pending ask: 0 turns them off, otherwise 300 (5 min) to 86400 (24 h). Unset inherits: the recipient's, then the org's, then 600 (10 min). An ask is reminded at most 3 times and never within 2 min of its expiry. One whose life (expiry minus its first push) is under this plus 2 min is reminded once, at the midpoint of its life, if it lives at least 3 min (a 300 s ask at 150 s). Set by recipients and orgs, not agents. |

Unknown properties are refused.
