# Technical Documentation: usekaneo/kaneo

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/usekaneo/kaneo)

### Overview (`README.md`)

<p align="center">
  <a href="https://kaneo.app">
    <img src="https://assets.kaneo.app/logo-text.png" alt="Kaneo's logo" width="450" />
  </a>
</p>

<div align="center">

[](LICENSE)
[](https://github.com/usekaneo/kaneo/actions)
[](https://github.com/usekaneo/kaneo/blob/main/AI_POLICY.md)
[](https://discord.gg/rU4tSyhXXU)
[](https://github.com/sponsors/andrejsshell)
</div>

<div align="center">
  <h3>
    <a href="https://cloud.kaneo.app">Cloud</a>
    <span> | </span>
    <a href="https://kaneo.app/docs/core/installation">Installation</a>
    <span> | </span>
    <a href="https://kaneo.app">Website</a>
    <span> | </span>
    <a href="https://discord.gg/rU4tSyhXXU">Discord</a>
  </h3>
</div>

<p align="center">
  Fast, simple, open-source project management.
</p>

<p align="center">
  <img src="screenshots/readme@1x.png" srcset="screenshots/readme@1x.png 1x, screenshots/readme.png 2x" alt="Kaneo project board with the Kaneo logo and tagline" />
</p>

## Built for your team's work

- **See work your way:** Board, List, Calendar, and Gantt views, plus a backlog, filters, and search.
- **Keep task context together:** descriptions, attachments, subtasks, dependencies, labels, comments, and time tracking.
- **Work with your team:** shared workspaces, invitations, custom roles, live updates, and public project views.
- **Shape your workflow:** custom columns and fields, with rules that move tasks when repository activity changes.
- **Stay informed:** in-app notifications, email, ntfy, Gotify, and personal webhooks.
- **Connect other tools:** GitHub and Gitea, chat integrations, project webhooks, a [REST API](https://kaneo.app/docs/api-reference/introduction), a [command-line client](https://kaneo.app/docs/cli), and [MCP](https://kaneo.app/docs/core/integrations/mcp) for AI assistants.

## Installation

- **Cloud:** [Get started with Kaneo Cloud](https://cloud.kaneo.app).
- **Self-hosted:** Follow the [installation guide](https://kaneo.app/docs/core/installation).

## Contributing

Code, translations, documentation, and bug reports are welcome. Start with the [contributing guide](CONTRIBUTING.md) and [local development setup](ENVIRONMENT_SETUP.md). Pay particular attention to [contribution eligibility](https://github.com/usekaneo/kaneo/blob/main/CONTRIBUTING.md#contribution-eligibility).

Join us on [Discord](https://discord.gg/rU4tSyhXXU), report bugs in [GitHub Issues](https://github.com/usekaneo/kaneo/issues), or submit and vote on feature requests at [Kaneo Feedback](https://feedback.kaneo.app).

## Sponsors

Kaneo is open source. If you find it useful, consider [sponsoring the project](https://github.com/sponsors/andrejsshell) to help support ongoing development.

### Partners

<p>
  <img src="apps/site/public/images/blacksmith-powered.png" alt="CI powered by Blacksmith" width="368" />
</p>

This project is tested with BrowserStack.

### Community sponsors

<a href="https://github.com/danielsada"><img src="https:&#x2F;&#x2F;github.com&#x2F;danielsada.png" width="60px" alt="User avatar: Daniel Sada" /></a><a href="https://github.com/randoneering"><img src="https:&#x2F;&#x2F;github.com&#x2F;randoneering.png" width="60px" alt="User avatar: (justin)randoneering" /></a><a href="https://github.com/ryanilano"><img src="https:&#x2F;&#x2F;github.com&#x2F;ryanilano.png" width="60px" alt="User avatar: Ryan Ilano" /></a><a href="https://github.com/bojanmilevski"><img src="https:&#x2F;&#x2F;github.com&#x2F;bojanmilevski.png" width="60px" alt="User avatar: Bojan Milevski" /></a>

## License

MIT License - see [LICENSE](LICENSE) for details.

---

<div align="center">
  <img src="https://repobeats.axiom.co/api/embed/3e8367ec2b2350e4fc48662df33c81dac657b833.svg" alt="Repobeats analytics image" />
</div>

<p align="center">
  Built with ❤️ by the Kaneo team and <a href="https://github.com/usekaneo/kaneo/graphs/contributors">contributors</a>
</p>

---

### Overview (`sentry/README.md`)

# Sentry

This directory holds Sentry configuration as code, so the alert rules and dashboards that Kaneo depends on are version-controlled and reproducible.

## Contents

- `alerts.json` — alert rules. Six rules cover first-seen issues, error spikes, missed cron check-ins, slow p95 latency, and MCP error rate. Both `kaneo-api` and `kaneo-web` are covered.
- `dashboards.json` — four Kaneo-specific dashboards (Backend, Frontend, MCP, Cron Monitors) with widgets for the metrics above. The prebuilt Sentry templates are left untouched.

## What's NOT here yet

- Cron monitor alert. The cron jobs in `apps/api/src/scheduler/index.ts` call `Sentry.captureCheckIn` with stable slugs (`due-date-reminders`, `project-webhook-reminders`, `seat-reconciliation`, `trial-reminders`). The Sentry monitors are auto-created the first time each job runs after the next deploy. Once they exist, the next run of `provision-sentry-alerts.sh` will resolve the slugs and create the cron-missed workflow.

## Applying the alerts

### Option A: provisioning script (recommended)

The script lives at `scripts/provision-sentry-alerts.sh`. It reads `sentry/alerts.json`, finds existing workflows by name, and **post-or-puts** so edited specs propagate. 

```bash
# 1. Generate a Sentry auth token (admin scope)
#    https://sentry.io/settings/account/api/auth-tokens/
#    Scope: org:write (or alerts:write)

# 2. Apply
SENTRY_API_TOKEN=sntrys_... ./scripts/provision-sentry-alerts.sh

# Dry-run first to see what would be created or updated
SENTRY_API_TOKEN=sntrys_... ./scripts/provision-sentry-alerts.sh --dry-run
```

The script targets the EU region (`https://de.sentry.io`). If Kaneo ever moves to the US region, change the `region` field in `alerts.json` (or override `SENTRY_API_BASE`).

If you rename an alert in `alerts.json`, the old name stays in Sentry alongside the new one. Rename alerts only when you intend to delete the old one manually.

### Option B: manual setup via the Sentry UI

For each rule in `alerts.json`, walk through Settings > Alerts > Create Alert. The JSON is human-readable enough to drop in to the Sentry UI form fields. Tables of the conditions are at the top of `alerts.json`; the `_comment_*` keys are stripped before applying.

## After applying

The script creates the rules but does NOT wire up notifications. Each rule lands in Sentry with an empty `actions` array so you can choose the right channel per rule. To finish setup:

1. Settings > Alerts > [rule name] > Actions
2. Pick a notification target. Recommended:
   - Slack `#engineering` for spike/latency/cron-missed
   - Email to on-call for new issues and MCP errors
3. Save.

Once notifications are wired, the rules start firing based on the events flowing through `Sentry.captureException` (anywhere in the codebase) and `Sentry.captureCheckIn` (the four cron jobs).

## Schema notes

Two rule families:

- **Issue rules** (`kind: "issue"`) — trigger on event condition types (first_seen_event, regression_event, etc.). One JSON entry per rule.
- **Metric rules** (`kind: "metric"`) — trigger on aggregate conditions (count, p95, failure_rate). Each entry creates a Detector (the metric source) and a Workflow (the alert) and links them via `detectorIds`.

## Applying the dashboards

`scripts/provision-sentry-dashboards.sh` reads `sentry/dashboards.json` and creates-or-updates each dashboard by title. The script preserves any filter settings (projects, environment, period) the maintainer has set in the Sentry UI by reading the current dashboard first and only swapping the widgets array.

```bash
SENTRY_API_TOKEN=sntrys_... ./scripts/provision-sentry-dashboards.sh
SENTRY_API_TOKEN=sntrys_... ./scripts/provision-sentry-dashboards.sh --dry-run
```

The prebuilt Sentry dashboards (Backend Overview, Frontend Overview, etc.) are **not** touched. Kaneo-specific dashboards are created with the `Kaneo:` prefix.

## Schema notes

### Alerts

Two rule families:

- **Issue rules** (`kind: "issue"`) — trigger on event condition types (first_seen_event, regression_event, etc.). One JSON entry per rule.
- **Metric rules** (`kind: "metric"`) — trigger on aggregate conditions (count, p95, failure_rate). Each entry creates a Detector (the metric source) and a Workflow (the alert) and links them via `detectorIds`.

The cron-missed-check-in rule is special: it doesn't create a Detector because the cron monitors already exist. The script resolves the slug strings in `detector_slugs` to numeric IDs via `GET /api/0/organizations/{org}/monitors/`. If the cron jobs haven't run yet, the monitors don't exist and the script will skip with a warning.

### Dashboards

Each dashboard entry has:
- `title` — dashboard name
- `widgets` — array of `{ title, displayType, interval, queries }` objects
- `queries` — array of `{ fields, aggregates, columns, query, orderby, limit }`
  - `fields` — columns to display
  - `aggregates` — aggregate functions to compute (e.g., `count()`, `p95(transaction.duration)`)
  - `columns` — group-by fields
  - `query` — Sentry search filter (e.g., `event.type:error project:kaneo-api`)
  - `orderby` — sort field (e.g., `-count()`)
  - `limit` — required on every query; max is 10. The script uses 10 for all queries.

Performance metrics use `event.type:transaction is_transaction:true` (the dataset migration removed `dataset: transactions`). Error metrics use `event.type:error`. Cron check-in events use `event.type:transaction monitor.check_in_id:*`.

The Sentry API surface used is documented at:
- <https://docs.sentry.io/api/monitors/create-an-alert-for-an-organization/>
- <https://docs.sentry.io/api/monitors/create-a-monitor-for-a-project/>
- <https://docs.sentry.io/api/dashboards/create-a-new-dashboard-for-an-organization/>

---

### Overview (`apps/docs/README.md`)

# Kaneo documentation

The docs are authored in MDX and rendered by Mintlify. `docs.json` owns navigation; `openapi.json` supplies the generated API reference.

## Preview and validate

From `apps/docs`:

```bash
pnpm dlx mint dev
pnpm dlx mint validate
pnpm dlx mint broken-links
```

The preview runs at `http://localhost:3000`. Use `--port 3107` if that port is busy. Search in the local preview may require a Mintlify login.

## Organization

- **Using Kaneo:** first project, task views, team management, account settings.
- **Self-hosting:** installation, configuration, sign-in, backups, upgrades, troubleshooting.
- **Integrations:** repositories, team channels, webhooks, and MCP.
- **Developers:** first API request, authentication, protocol details, endpoint reference.

Existing `core/` page paths are retained so external links keep working. Put a page in the navigation where readers expect to find it rather than moving its URL for cosmetic consistency.

See [CONTRIBUTING.md](CONTRIBUTING.md) for writing and image guidelines. Product captures and regeneration instructions live in [images/product/README.md](images/product/README.md).

---

### Overview (`apps/mobile/README.md`)

# Kaneo Mobile

The Kaneo app for Android and iOS, built with Expo and React Native. Works with **Kaneo Cloud** (`cloud.kaneo.app`) and **self-hosted** instances.

## Features

- **Sign in** with email and password, a one-time email code, an API key, or **Continue in browser** (device authorization). The browser flow covers GitHub, Google, Discord, OIDC/SSO and anything else the server supports.
- **Workspaces**: switch, create, rename, describe, leave, delete.
- **Members**: list, invite with a role, resend or cancel invitations, change roles, remove. Accept or decline your own invitations.
- **Projects**: list with progress, create, rename, change the key, make public, archive or restore, move to another workspace, delete.
- **Boards**: swipe between columns, or use the list view. Show the backlog and archived tasks. Filter by text, assignee, priority, label or overdue. Long-press a card to move it, change its priority or assignee, or send it to the top or bottom of its column.
- **Columns**: add, rename, reorder, mark as done, delete.
- **Tasks**: create with every field (including custom fields), edit the title, Markdown description (with preview and image upload), status, priority, assignee, start and due dates, and labels (you can create labels inline). Duplicate, move to another project, share, copy the link, delete.
- **Subtasks and relations**: add subtasks, check them off, set or change the parent task, link related or blocking tasks.
- **Links**: attach URLs; synced integration links are shown read-only.
- **Custom fields**: text, number, date, checkbox, dropdown, multi-select. Define them in project settings.
- **Time tracking**: start/stop a timer, log time manually, edit your entries.
- **Comments and activity**: Markdown comments with @mentions and image attachments; edit and delete your own comments; full activity timeline.
- **My tasks**, **Inbox** (notifications: mark read, mark all read, clear) and the **workspace activity** feed.
- **Search** across tasks, projects and comments.
- **Account**: name, profile photo, password, notification delivery settings (email, ntfy, Gotify, webhook, event types, due-date reminders).
- **Live updates** over Kaneo's WebSockets while the app is open, and **phone notifications** from a background check (Kaneo has no mobile push service). Android runs the check about every 15 minutes; iOS decides when to run it, so it can be less frequent.

### Left out on purpose

These are admin or desktop tasks that don't fit a phone: integration setup (GitHub, GitLab, Gitea, Slack, Discord, Telegram, Mattermost, webhooks) and sync rules, workflow rules, task import/export, calendar feeds, instance administration, custom role editing, per-member project access, billing, API key management, MCP, project icons and backgrounds, and Gantt/calendar views. They are still available on the web.

## Connecting

- **Kaneo Cloud**: tap _Kaneo Cloud_.
- **Self-hosted**: enter the address you open Kaneo at, e.g. `kaneo.example.com` or `http://192.168.1.20:1337`. If your web app and API are on different hosts, enter the **API** address. `http://` is allowed for LAN/VPN servers on both platforms.

_Continue in browser_ uses the device-authorization client `kaneo-cli`, which Kaneo accepts by default. If an instance sets `DEVICE_AUTH_CLIENT_IDS`, it must include `kaneo-cli` for this option to work. Password, email code and API key sign-in work regardless.

On Kaneo Cloud, creating an account and requesting an email code are protected by a browser captcha, so the app opens the website for sign-up; password sign-in and browser sign-in work in the app.

Credentials are stored with `expo-secure-store`: in the iOS Keychain, and encrypted with a key held in the Android Keystore.

## Development

Install dependencies from the repository root with `pnpm install`. The app uses native modules, so run it as a development build rather than in Expo Go:

```bash
pnpm --filter @kaneo/mobile android   # needs JDK 17 and the Android SDK
pnpm --filter @kaneo/mobile ios       # needs macOS, Xcode and CocoaPods
pnpm --filter @kaneo/mobile start     # Metro only, for an already installed development build
```

The `ios/` and `android/` folders are generated from `app.json` and the config plugins in `plugins/` (continuous native generation) and are not committed. Change native settings there, then regenerate with `pnpm --filter @kaneo/mobile exec expo prebuild --clean`.

Checks:

```bash
pnpm --filter @kaneo/mobile lint
pnpm --filter @kaneo/mobile typecheck
pnpm --filter @kaneo/mobile test
```

## Release builds

The **Android APK** workflow (`.github/workflows/android.yml`) builds a release APK and uploads it as a workflow artifact. It runs on pull requests that touch `apps/mobile` and can be started by hand. Bump `version` and `android.versionCode` in `app.json` for each APK you hand out; Android only installs an update with a higher `versionCode`.

Release builds are signed with the key named by `KANEO_ANDROID_KEYSTORE`, `KANEO_ANDROID_KEYSTORE_PASSWORD`, `KANEO_ANDROID_KEY_ALIAS` and `KANEO_ANDROID_KEY_PASSWORD` (see `plugins/with-release-signing.js`), and with the debug key when those are unset. An APK only installs over one signed with the same key, so create the release key once and keep it safe:

```bash
keytool -genkeypair -v -keystore kaneo-release.keystore -alias kaneo \
  -keyalg RSA -keysize 4096 -validity 10000
base64 -i kaneo-release.keystore | pbcopy   # paste into the ANDROID_KEYSTORE_BASE64 secret
```

CI reads the repository secrets `ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS` and `ANDROID_KEY_PASSWORD`. Locally, export the `KANEO_ANDROID_*` variables and run `pnpm --filter @kaneo/mobile exec expo run:android --variant release`. For iOS, build with `pnpm --filter @kaneo/mobile exec expo run:ios --configuration Release`.

## Project layout

```
src/app/         Expo Router screens; every file is a route
src/components/  shared UI (sheets, pickers, Markdown, avatars, toasts)
src/features/    screen-specific pieces for the home tabs, board and task
src/lib/         API client and types, auth, session storage, realtime, background notifications
src/theme/       Kaneo design tokens, light and dark
plugins/         local Expo config plugins
```

---

### Overview (`charts/kaneo/README.md`)

# Kaneo Helm Chart
This Helm chart deploys [Kaneo](https://kaneo.app) - open source project management that works for you, not against you.
## Introduction
This chart bootstraps a Kaneo deployment on a Kubernetes cluster using the Helm package manager. It deploys both the API backend and Web frontend components, along with a PostgreSQL database, with optional ingress or Gateway API resources.
## Prerequisites
- Kubernetes 1.23+
- Helm 3.2.0+
- PV provisioner support in the underlying infrastructure (if persistence is enabled)
## Quick Start
### Basic Installation
Install directly from GHCR:
```bash
helm install kaneo oci://ghcr.io/usekaneo/charts/kaneo \
  --namespace kaneo \
  --create-namespace
# Access locally
kubectl port-forward svc/kaneo-kaneo 5173:5173 -n kaneo
```
Open [http://localhost:5173](http://localhost:5173) and you're ready to go.
### Production Setup with Ingress
For real deployments, you'll want proper ingress:
```bash
helm install kaneo oci://ghcr.io/usekaneo/charts/kaneo \
  --namespace kaneo \
  --create-namespace \
  --set ingress.enabled=true \
  --set ingress.className=nginx \
  --set "ingress.hosts[0].host=pm.yourcompany.com"
```
### Production Setup with Gateway API
If your cluster already has Gateway API CRDs and a `Gateway` configured, you can expose Kaneo with an `HTTPRoute`:
```bash
helm install kaneo oci://ghcr.io/usekaneo/charts/kaneo \
  --namespace kaneo \
  --create-namespace \
  --set gateway.enabled=true \
  --set "gateway.parentRefs[0].name=main-gateway" \
  --set "gateway.parentRefs[0].namespace=gateway-system" \
  --set "gateway.parentRefs[0].sectionName=https" \
  --set "gateway.hostnames[0]=pm.yourcompany.com"
```
## Installing the Chart
To install the published chart with the release name `my-kaneo`:
```bash
helm install my-kaneo oci://ghcr.io/usekaneo/charts/kaneo
```
To install from a local checkout instead:
```bash
helm install my-kaneo ./charts/kaneo
```
The command deploys Kaneo on the Kubernetes cluster with default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation.
## Uninstalling the Chart
To uninstall/delete the `my-kaneo` deployment:
```bash
helm uninstall my-kaneo
```
## Parameters
### Global parameters
| Name                     | Description                                                                                                        | Value       |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------ | ----------- |
| `nameOverride`           | String to partially override the fullname template (will maintain the release name)                                | `""`        |
| `fullnameOverride`       | String to fully override the fullname template                                                                     | `""`        |
| `replicaCount`           | Number of replicas (ignored if autoscaling is enabled)                                                             | `1`         |
| `enableServiceLinks`     | Controls Pod's enableServiceLinks field                                                                            | `false`   |
| `topologySpreadConstraints` | Controls Pod's topologySpreadConstraints field                                                                         | `[]`   |
### Autoscaling parameters
| Name                                | Description                                                                                                        | Value       |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------- |
| `autoscaling.enabled`               | Enable autoscaling for the deployment                                                                              | `false`     |
| `autoscaling.minReplicas`           | Minimum number of replicas                                                                                         | `1`         |
| `autoscaling.maxReplicas`           | Maximum number of replicas                                                                                         | `10`        |
| `autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization percentage                                                                         | `80`        |
When CPU autoscaling is enabled, set `kaneo.resources.requests.cpu`; Kubernetes cannot calculate CPU utilization without a CPU request.
### PostgreSQL Database parameters
| Name                                | Description                                                                                                        | Value                           |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------- |
| `postgresql.enabled`                | Deploy PostgreSQL as part of this chart                                                                           | `true`                          |
| `postgresql.image.repository`       | PostgreSQL image repository                                                                                        | `postgres`                      |
| `postgresql.image.tag`              | PostgreSQL image tag                                                                                               | `16-alpine`                     |
| `postgresql.image.pullPolicy`       | PostgreSQL image pull policy                                                                                      | `IfNotPresent`                  |
| `postgresql.auth.database`          | PostgreSQL database name                                                                                           | `kaneo`                         |
| `postgresql.auth.username`          | PostgreSQL username                                                                                                | `kaneo_user`                    |
| `postgresql.auth.password`          | PostgreSQL password                                                                                                | `kaneo_password`                |
| `postgresql.auth.existingSecret`    | Name of existing secret containing PostgreSQL credentials                                                          | `""`                            |
| `postgresql.persistence.enabled`    | Enable persistence for PostgreSQL data                                                                             | `true`                          |
| `postgresql.persistence.size`       | PostgreSQL PVC size                                                                                                | `8Gi`                           |
| `postgresql.persistence.storageClass` | PostgreSQL PVC storage class                                                                                     | `""`                            |
| `postgresql.persistence.accessMode` | PostgreSQL PVC access mode                                                                                         | `ReadWriteOnce`                 |
| `postgresql.service.type`           | PostgreSQL service type                                                                                            | `ClusterIP`                     |
| `postgresql.service.port`           | PostgreSQL service port                                                                                            | `5432`                          |
| `postgresql.resources`              | Resource requests and limits for PostgreSQL container                                                              | `{}`                            |
### Kaneo application parameters
| Name                                | Description                                                                                                        | Value                           |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------- |
| `kaneo.image.repository`            | Kaneo image repository                                                                                             | `ghcr.io/usekaneo/kaneo`        |
| `kaneo.image.tag`                   | Kaneo image tag. Defaults to `Chart.appVersion` when empty                                                         | `""`                            |
| `kaneo.image.pullPolicy`            | Kaneo image pull policy                                                                                            | `IfNotPresent`                  |
| `kaneo.service.type`                | Kaneo service type                                                                                                 | `ClusterIP`                     |
| `kaneo.service.port`                | Kaneo service port                                                                                                 | `5173`                          |
| `kaneo.service.targetPort`          | Kaneo container port                                                                                               | `5173`                          |
| `kaneo.env`                         | Environment variables for the Kaneo container                                                                      | See `values.yaml`               |
| `kaneo.env.clientUrl`               | Public URL of the Kaneo instance. **Required for every deployment**; sets `KANEO_CLIENT_URL`. Helm rejects missing values and URLs with credentials, paths, queries or fragments before creating resources. Note: this key is case-sensitive (`clientUrl`, not `clientURL`). | `""` |
| `kaneo.env.corsOrigins`             | Allowed CORS origins as a comma-separated string or YAML list                                                      | `[]`                            |
| `kaneo.env.authSecret`              | Required Better Auth secret (minimum 32 characters), stored in a chart-managed Secret; ignored if existingSecret is enabled                           | `""` |
| `kaneo.env.existingSecret.enabled`  | Whether to use an existing secret for `AUTH_SECRET`                                                                | `false`                         |
| `kaneo.env.existingSecret.name`     | Name of the existing secret containing `AUTH_SECRET`                                                               | `""`                            |
| `kaneo.env.existingSecret.key`      | Key in the existing secret that contains `AUTH_SECRET`                                                             | `auth-secret`                   |
| `kaneo.env.disableRegistration`     | Disable new user registration                                                                                      | `false`                         |
| `kaneo.env.disablePasswordRegistration` | Disable password-based account creation while keeping social/OIDC registration available                        | `false`                         |
| `kaneo.env.disableEmailOtpSignIn`   | Use email/password sign-in instead of verification codes when SMTP is configured                                   | `false`                         |
| `kaneo.env.database.external.enabled` | Use external PostgreSQL database (set postgresql.enabled to false)                                               | `false`                         |
| `kaneo.env.database.external.host`  | External PostgreSQL host                                                                                           | `""`                            |
| `kaneo.env.database.external.port`  | External PostgreSQL port                                                                                           | `5432`                          |
| `kaneo.env.database.external.database` | External PostgreSQL database name                                                                               | `kaneo`                         |
| `kaneo.env.database.external.username` | External PostgreSQL username                                                                                    | `kaneo_user`                    |
| `kaneo.env.database.external.password` | External PostgreSQL password                                                                                    | `""`                            |
| `kaneo.env.database.external.existingSecret.enabled` | Use an existing secret for the external database connection URI                             | `false`                         |
| `kaneo.env.database.external.existingSecret.name` | Name of the secret containing the database connection URI                                    | `""`                            |
| `kaneo.env.database.external.existingSecret.passwordKey` | Key in the secret whose value is a full PostgreSQL connection URI                       | `postgres_uri`                  |
| `kaneo.extraEnv`                    | Additional Kubernetes EnvVar entries appended to the Kaneo container                                               | `[]`                            |
| `kaneo.extraEnvFrom`                | Additional Kubernetes EnvFromSource entries appended to the Kaneo container                                        | `[]`                            |
| `kaneo.resources`                   | Resource requests and limits for the Kaneo container (optional, disabled by default)                               | `{}`                            |
| `podSecurityContext`                | Security context applied at the Pod level                                                                          | `{}`                            |
| `kaneo.securityContext`             | Security context applied at the Kaneo container level                                                              | `{}`                            |
### Ingress parameters
| Name                                | Description                                                                                                        | Value                           |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------- |
| `ingress.enabled`                   | Enable ingress                                                                                                     | `false`                         |
| `ingress.className`                 | Ingress class name                                                                                                 | `""`                            |
| `ingress.annotations`               | Ingress annotations                                                                                                | `{}`                            |
| `ingress.hosts`                     | Ingress hosts configuration                                                                                        | See `values.yaml`               |
| `ingress.tls`                       | Ingress TLS configuration                                                                                          | `[]`                            |
### Gateway API parameters
| Name                                | Description                                                                                                        | Value                           |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------- |
| `gateway.enabled`                   | Enable Gateway API `HTTPRoute` creation                                                                            | `false`                         |
| `gateway.annotations`               | Annotations added to the `HTTPRoute`                                                                               | `{}`                            |
| `gateway.labels`                    | Extra labels added to the `HTTPRoute`                                                                              | `{}`                            |
| `gateway.parentRefs`                | Parent Gateway listener references for attaching the `HTTPRoute`                                                   | `[]`                            |
| `gateway.hostnames`                 | Hostnames exposed by the `HTTPRoute`                                                                               | `[]`                            |
| `gateway.rules`                     | Gateway API routing rules mapping paths to the chart services                                                      | See `values.yaml`               |
## Configuration Examples
### Minimal Configuration
```yaml
# values.yaml
kaneo:
  env:
    authSecret: "your-secure-auth-secret-at-least-32-characters"
    clientUrl: "https://your-domain.com"
ingress:
  enabled: true
  className: nginx
  annotations: {}
  hosts:
    - host: your-domain.com
      paths:
        - path: /
          pathType: Prefix
          service: kaneo
          port: 5173
```
### Production Configuration with TLS
```yaml
# values.yaml
replicaCount: 1
# PostgreSQL configuration
postgresql:
  auth:
    password: "your-secure-db-password"
  persistence:
    size: 20Gi
    storageClass: "managed-premium"
  resources:
    limits:
      cpu: 500m
      memory: 512Mi
    requests:
      cpu: 100m
      memory: 128Mi
# Kaneo configuration
kaneo:
  resources:
    limits:
      cpu: 1000m
      memory: 1Gi
    requests:
      cpu: 200m
      memory: 256Mi
  env:
    authSecret: "your-secure-auth-secret-at-least-32-characters"
    clientUrl: "https://your-domain.com"
ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  hosts:
    - host: your-domain.com
      paths:
        - path: /
          pathType: Prefix
          service: kaneo
          port: 5173
  tls:
    - secretName: kaneo-tls
      hosts:
        - your-domain.com
```
### Production Configuration with Traefik
If you're running Traefik as your ingress controller (common in self-hosted clusters), configure the ingress directly rather than through the chart's built-in ingress support:
```yaml
# values.yaml
kaneo:
  env:
    authSecret: "your-secure-auth-secret-at-least-32-characters"
    clientUrl: "https://kaneo.your-domain.com"
```
```yaml
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kaneo-ingress
  namespace: kaneo
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: traefik
  rules:
    - host: kaneo.your-domain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: kaneo-kaneo
                port:
                  number: 5173
  tls:
    - hosts:
        - kaneo.your-domain.com
      secretName: kaneo-tls
```
### Using External PostgreSQL Database
If you prefer to use an external PostgreSQL database instead of the bundled one:
```yaml
# values.yaml
# Disable bundled PostgreSQL
postgresql:
  enabled: false
kaneo:
  env:
    authSecret: "your-secure-auth-secret-at-least-32-characters"
    database:
      external:
        enabled: true
        host: "your-postgres-host.com"
        port: 5432
        database: "kaneo"
        username: "kaneo_user"
        password: "your-db-password"
```
### Using an Existing Secret for Sensitive Data
For production environments, it's recommended to store sensitive data like the auth secret and database credentials in Kubernetes Secrets:
When `postgresql.auth.existingSecret` is used with bundled PostgreSQL, the password is expanded by Kubernetes into `DATABASE_URL` at runtime and must be URL-safe. If your password contains reserved URI characters such as `@`, `:`, `/`, `#`, `%`, or spaces, use an external database and provide the complete connection URI through `kaneo.env.database.external.existingSecret`.
```bash
# Create a Secret for sensitive data
kubectl create secret generic kaneo-secrets \
  --namespace kaneo \
  --from-literal=auth-secret="your-secure-auth-secret-at-least-32-characters" \
  --from-literal=postgres-password="your-secure-db-password"
```
Then reference these secrets in your values:
```yaml
# values.yaml
postgresql:
  auth:
    existingSecret: "kaneo-secrets"
    secretKeys:
      userPasswordKey: "postgres-password"
kaneo:
  env:
    existingSecret:
      enabled: true
      name: "kaneo-secrets"
      key: "auth-secret"
```
#### Using an Existing Secret with External PostgreSQL
When using an external database, the secret must contain a full PostgreSQL connection URI rather than just the password. Any special characters in the password (such as `@`, `:`, `/`) must be [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding).
```bash
# Create the secret with a full connection URI
kubectl create secret generic kaneo-secrets \
  --namespace kaneo \
  --from-literal=auth-secret="your-secure-auth-secret-at-least-32-characters" \
  --from-literal=postgres_uri="postgresql://kaneo_user:your-password@your-host:5432/kaneo"
```
```yaml
# values.yaml
postgresql:
  enabled: false

kaneo:
  env:
    clientUrl: "https://kaneo.your-domain.com"
    existingSecret:
      enabled: true
      name: "kaneo-secrets"
      key: "auth-secret"
    database:
      external:
        enabled: true
        host: "your-postgres-host.com"
        port: 5432
        database: "kaneo"
        username: "kaneo_user"
        password: ""
        existingSecret:
          enabled: true
          name: "kaneo-secrets"
          passwordKey: postgres_uri
```
## Database Management
### PostgreSQL Configuration
The chart deploys PostgreSQL 16 (Alpine) by default with the following configuration:
- Database name: `kaneo`
- Username: `kaneo_user`
- Default password: `kaneo_password` (change this in production!)
- Persistent storage: 8Gi (configurable)
The bundled PostgreSQL deployment is intended for development, trials, and small self-hosted installs. For production environments, use an external managed PostgreSQL database by setting `postgresql.enabled=false` and configuring `kaneo.env.database.external`.
Bundled PostgreSQL credentials are only applied when PostgreSQL initializes an empty data directory. If a PVC already exists, changing `postgresql.auth.password` or `postgresql.auth.existingSecret` updates the Pod environment but does not rotate the password inside the existing database. For local retesting with a new password, uninstall the release and delete the test PVC before reinstalling:
```bash
helm uninstall kaneo -n kaneo-test
kubectl delete pvc kaneo-postgresql-data -n kaneo-test --ignore-not-found
```
To preserve data, rotate the password inside PostgreSQL first, then update the Helm values to match.
### Backup and Recovery
For production deployments, consider implementing regular database backups:
```bash
# Example backup command
kubectl exec -it deployment/my-kaneo-postgresql -- pg_dump -U kaneo_user kaneo > kaneo-backup.sql
```
### Migration from SQLite
If you're migrating from a previous SQLite-based installation, you'll need to:
1. Export your data from SQLite
2. Deploy the new PostgreSQL-based chart
3. Import your data into PostgreSQL
Contact the Kaneo community on [Discord](https://discord.gg/rU4tSyhXXU) for migration assistance.
## Troubleshooting
### "invalid origin" error on login
Kaneo's API validates the `Origin` header on every request against `KANEO_CLIENT_URL`. The chart now requires `clientUrl` before rendering. On older deployments, an unset or incorrect value can cause login failures.

Set `clientUrl` to the URL users access Kaneo from:
```yaml
kaneo:
  env:
    clientUrl: "https://kaneo.your-domain.com"
```

> **Note:** The key is `clientUrl` (camelCase). `clientURL` (all-caps) does not configure this value; Helm reports the missing required `clientUrl`.

### Pods crash immediately after upgrading to use `existingSecret`
If pods enter `CrashLoopBackOff` after switching to an existing secret for the external database, the connection URI in the secret is likely wrong. Check what the pod is actually using:
```bash
kubectl get secret kaneo-secrets -n kaneo \
  -o jsonpath='{.data.postgres_uri}' | base64 -d
```
Common causes:
- Wrong password
- Password contains special characters that need percent-encoding (e.g. `p@ss` → `p%40ss`)
- Incorrect host, port, or database name in the URI

### PodSecurity warnings on install/upgrade
On clusters with Pod Security Admission enabled, you may see warnings like `would violate PodSecurity "restricted:latest"`. The chart exposes `podSecurityContext` and `kaneo.securityContext` to address these. See the [Security](#security) section for recommended values.
## Architecture
This chart deploys the following components:
1. **Kaneo application**: Serves the web UI and API from the combined Kaneo image
2. **PostgreSQL Database**: Stores all application data with proper relational integrity
The Kaneo application and PostgreSQL run in separate pods for resource isolation and simpler database lifecycle management.
## Production Environment
For production deployments, you should:
1. Set secure values for `AUTH_SECRET` and PostgreSQL passwords
2. Use an Ingress controller to expose the application
3. Configure TLS for secure access
4. Set appropriate resource limits and requests
5. Enable persistent storage with appropriate storage classes
6. Consider using external PostgreSQL for better scalability
```yaml
ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  hosts:
    - host: your-domain.com
      paths:
        - path: /
          pathType: Prefix
          service: kaneo
          port: 5173
  tls:
    - secretName: kaneo-tls
      hosts:
        - your-domain.com
```
## Using Gateway API
As an alternative to Ingress, the chart can create an `HTTPRoute` for the Kubernetes Gateway API:
```yaml
# values.yaml
gateway:
  enabled: true
  parentRefs:
    - name: main-gateway
      namespace: gateway-system
      sectionName: https
  hostnames:
    - kaneo.example.com
```
By default the chart creates one Gateway API rule:
1. `/` goes to the Kaneo service
If you need custom matching or multiple backend references, override `gateway.rules` directly. Each `backendRefs` entry follows the same pattern as ingress and uses the chart-specific `service` field (`kaneo`), which is expanded to the release-specific Service name.
## Security
For production deployments, consider the following security recommendations:
1. Use secure `AUTH_SECRET` and PostgreSQL passwords, preferably stored in Kubernetes Secrets
2. Enable TLS for ingress or Gateway API
3. Enable and set resource limits to prevent resource exhaustion
4. Use a dedicated storage class for the PostgreSQL database
5. Consider using a network policy to restrict traffic between components
6. Regularly update PostgreSQL and application images
### Pod Security Context
On clusters with Pod Security Admission enforcement, set the following to satisfy the `restricted` policy:
```yaml
podSecurityContext:
  runAsNonRoot: true
  seccompProfile:
    type: RuntimeDefault

kaneo:
  securityContext:
    allowPrivilegeEscalation: false
    capabilities:
      drop: ["ALL"]
```
### OpenShift and read-only containers
Kaneo supports OpenShift's assigned UID. The chart mounts an `emptyDir` at `/var/lib/kaneo` for generated frontend assets, nginx state, and temporary files. These files are recreated at startup; application data remains in PostgreSQL and the configured object storage.

Leave `runAsUser`, `runAsGroup`, and `fsGroup` unset on OpenShift so its security policy can assign permitted IDs. With an external PostgreSQL database, the Kaneo container can use:
```yaml
podSecurityContext:
  runAsNonRoot: true
  seccompProfile:
    type: RuntimeDefault

kaneo:
  securityContext:
    readOnlyRootFilesystem: true
    allowPrivilegeEscalation: false
    capabilities:
      drop: ["ALL"]
```
The bundled PostgreSQL image has its own platform requirements; use an OpenShift-compatible database deployment or an external database on OpenShift.

When running the combined or standalone web image outside Helm with a read-only root filesystem, mount writable storage at `/var/lib/kaneo`. For example:
```bash
docker run --read-only \
  --tmpfs /var/lib/kaneo:rw,mode=0770,uid=1001,gid=0 \
  --env-file .env -p 5173:5173 ghcr.io/usekaneo/kaneo:latest
```
For a bind mount, grant write access to the container's UID or a group it belongs to. The image's default user is UID 1001 with GID 0; arbitrary UIDs can write its runtime directory through GID 0 when the root filesystem is writable.

nginx serves the web app from `/var/lib/kaneo/html`, where startup writes the runtime configuration, and reads server configuration copied from `/etc/nginx/conf.d` into `/var/lib/kaneo/nginx/conf.d`. If you mount your own server configuration, set `root /var/lib/kaneo/html;`. The previous `/usr/share/nginx/html` still holds the unconfigured build, so a config that serves it breaks API calls and sign-in; startup logs a warning when a config still uses that path. Files you mount into `/usr/share/nginx/html`, such as a custom favicon, are still copied into `/var/lib/kaneo/html` and served, and startup never writes to them. Templates in `/etc/nginx/templates` still render at startup, and `default.conf.template` replaces the bundled server configuration.

### Registration Control
By default, user registration is enabled. To disable new user registration:
```yaml
kaneo:
  env:
    disableRegistration: true
```
This will prevent new users from registering while still allowing existing users to log in. The registration option will be hidden from the login page.

### Authentication secret storage

The chart stores `kaneo.env.authSecret` in a Kubernetes Secret and the application Deployment references its `auth-secret` key. Upgrades preserve the supplied value; the chart does not generate a new signing secret. For production, prefer `kaneo.env.existingSecret` so secret material is not supplied in Helm values or retained in Helm release metadata. Limit access to Secrets and Helm release records.

When upgrading from a chart that placed `AUTH_SECRET` directly in the Deployment, consider the old Deployment, ReplicaSets, exported manifests and deployment logs exposed to readers of those resources. If those readers were not authorized to possess the signing secret, rotate it and invalidate affected authentication artifacts as part of the upgrade. Moving the value into a Secret does not revoke earlier copies.

---

### Overview (`packages/cli/README.md`)

# Kaneo CLI

The official command-line client for [Kaneo](https://kaneo.app), the open source project manager. It is maintained in the [usekaneo/kaneo](https://github.com/usekaneo/kaneo) monorepo and ships as a single `kaneo` binary for macOS, Linux and Windows.

```bash
curl -fsSL https://kaneo.app/cli/install.sh | sh
kaneo login
kaneo task list
```

It works with Kaneo Cloud and with self-hosted instances, and prints styled output in a terminal and plain JSON everywhere else.

## Install

macOS and Linux:

```bash
curl -fsSL https://kaneo.app/cli/install.sh | sh
```

Windows (PowerShell):

```powershell
irm https://kaneo.app/cli/install.ps1 | iex
```

With Homebrew, or with npm and Node.js 20 or newer:

```bash
brew install usekaneo/tap/kaneo
npm install @kaneo/cli
```

The script picks the build for your system from the `cli-v<version>` GitHub release, verifies it against the release's `SHA256SUMS`, and installs it to `~/.local/bin/kaneo` (Windows: `%LOCALAPPDATA%\Programs\kaneo\kaneo.exe`, added to your user `PATH`). Both scripts are plain text, so you can read [install.sh](https://github.com/usekaneo/kaneo/blob/main/apps/site/public/cli/install.sh) and [install.ps1](https://github.com/usekaneo/kaneo/blob/main/apps/site/public/cli/install.ps1) before running them.

| Variable | What it does |
| --- | --- |
| `KANEO_VERSION` | Install this version, such as `0.1.0` or `cli-v0.1.0`, instead of the latest |
| `KANEO_INSTALL_DIR` | Install into this directory |
| `KANEO_DOWNLOAD_URL` | Download from a mirror that has the GitHub release layout (`<url>/cli-v<version>/<file>`) instead of GitHub. Requires `KANEO_VERSION` |

```bash
curl -fsSL https://kaneo.app/cli/install.sh | KANEO_VERSION=0.1.0 sh
curl -fsSL https://kaneo.app/cli/install.sh | KANEO_INSTALL_DIR="$HOME/bin" sh
curl -fsSL https://kaneo.app/cli/install.sh | KANEO_DOWNLOAD_URL=https://mirror.example.com/kaneo KANEO_VERSION=0.1.0 sh
```

```powershell
$env:KANEO_VERSION = "0.1.0"; irm https://kaneo.app/cli/install.ps1 | iex
```

To update, run the install command again, `brew upgrade kaneo` for Homebrew, or `npm install @kaneo/cli@latest` for npm. To uninstall, delete the binary, and `~/.config/kaneo` too if you want to remove your logins.

Prefer the script to downloading a binary from the release page with a browser: the macOS builds are signed ad hoc but not notarized, so Gatekeeper can block browser downloads. On Alpine and other musl distributions, install the C++ runtime first with `apk add libstdc++`. See the [install guide](https://kaneo.app/docs/cli/install) for mirrors, offline installs and supported systems.

## Sign in

`kaneo login` uses device authorization: it shows a one-time code, copies it to your clipboard, and opens the approval page in your browser. Approve it there and the CLI stores a token for that server.

```bash
kaneo login                                   # Kaneo Cloud
kaneo login --api-url https://kaneo.example.com
kaneo whoami
kaneo logout
```

For CI and scripts, use an API key instead (create one in Settings, then Account, then API Keys):

```bash
export KANEO_API_KEY=...
kaneo task list -w <workspace-id> -p KAN
```

Credentials are resolved in this order: `--token`, then `KANEO_API_KEY`, then the stored login. A stored login is only sent to the server it was created for.

## Commands

| Command | What it does |
| --- | --- |
| `kaneo` | Home screen: your account, workspace, open tasks and the main commands |
| `kaneo login`, `logout`, `whoami` | Sign in, sign out, show the current account and server |
| `kaneo profile list`, `use`, `rename`, `remove` | Manage stored logins for several servers or accounts |
| `kaneo task list`, `mine`, `view`, `open` | List a project's tasks or your own, show one, open it in the browser |
| `kaneo task create`, `edit`, `duplicate`, `delete` | Create, change, copy and delete tasks |
| `kaneo task status`, `move`, `assign`, `field` | Change a task's column, project, assignee or custom fields |
| `kaneo task bulk` | Apply one change to many tasks at once |
| `kaneo task attach`, `images` | Upload files to a task, show the images in its description |
| `kaneo task relation`, `link`, `activity` | Subtasks and blockers, links, and the task's history |
| `kaneo task export`, `import` | Export a project's tasks as JSON and create tasks from such a file |
| `kaneo comment list`, `add`, `edit`, `delete` | Read and manage task comments |
| `kaneo label list`, `create`, `edit`, `delete` | Manage workspace labels |
| `kaneo time start`, `stop`, `status`, `log`, `list`, `edit` | Track time with a timer or log it afterwards |
| `kaneo project list`, `view`, `create`, `edit`, `archive`, `unarchive`, `move`, `delete` | Manage projects |
| `kaneo column list`, `create`, `edit`, `move`, `delete` | Manage a project's columns |
| `kaneo field list`, `create`, `delete` | Manage a project's custom fields |
| `kaneo workflow list`, `set`, `delete` | Move tasks when GitHub, GitLab or Gitea events arrive |
| `kaneo workspace list`, `use`, `create`, `edit`, `leave`, `delete` | Choose and manage workspaces |
| `kaneo member list`, `invite`, `role`, `remove` | Manage workspace members |
| `kaneo invitation list`, `pending`, `accept`, `decline`, `cancel` | Manage invitations you sent or received |
| `kaneo notification list`, `read` | Read your notifications (also `kaneo inbox`) |
| `kaneo search` | Search tasks, projects and comments |
| `kaneo link`, `unlink` | Save or remove the workspace and project for a repository |
| `kaneo context` | Show the settings in use and where each came from |
| `kaneo doctor` | Check the server, your login and API compatibility |
| `kaneo api` | Send an authenticated request to any API endpoint, like `gh api` |

The [command reference](https://kaneo.app/docs/cli/reference) lists every argument and flag. Run any command with `--help` for the same information, and `kaneo --completions zsh` (or `bash`, `fish`, `sh`) to print a shell completion script.

Tasks are addressed by their ticket ID, such as `KAN-123`, or by id. When a required workspace, project or column is missing and you are in a terminal, the CLI offers a searchable picker. In scripts it fails with an error that says which flag to pass.

Commands that delete or remove data ask for confirmation in a terminal and need `--yes` in scripts. Comments and descriptions can be read from a file with `-F <file>`, or from stdin with `-`.

## Output

Human mode is the default in a terminal. JSON mode is used with `--json` or `--jq`, with `KANEO_JSON=true`, or whenever stdout is not a terminal:

- A successful command prints exactly one JSON value on stdout.
- A failed command prints `{"error": "message"}` on stdout and exits with code 1.
- JSON mode never prints colors, spinners, images or extra lines.

`--jq` filters the JSON with a built-in jq engine, so `jq` does not need to be installed. Strings print without quotes, one result per line:

```bash
kaneo task list -p KAN --jq '.[] | select(.priority == "urgent") | .ticketId'
```

Use `--human` to force styled output. Styled output respects `NO_COLOR`, `FORCE_COLOR` and `TERM=dumb`, and falls back to ASCII when the terminal cannot show Unicode.

Images in task descriptions (`kaneo task images`), uploads (`kaneo task attach`) and your avatar (`kaneo whoami`) are shown inline in Kitty, Ghostty, iTerm2 and WezTerm. Other terminals with 256 colors and Unicode get a preview drawn with colored blocks, and the rest get links. Set `KANEO_IMAGES=kitty|iterm|blocks|off` to override this. Your token is only sent with requests for files on your Kaneo server.

## Agent skill

The [`kaneo-cli` skill](https://github.com/usekaneo/kaneo/tree/main/skills/kaneo-cli) teaches coding agents such as Claude Code, Codex and Cursor to use the CLI: check the login and workspace, read JSON output, address tasks by ticket ID, and confirm before deleting anything.

```bash
npx skills add usekaneo/kaneo --skill kaneo-cli
```

## Configuration

| Setting | Flag | Environment |
| --- | --- | --- |
| Server | `--api-url` | `KANEO_API_URL` (default `https://cloud.kaneo.app`) |
| Token | `--token` | `KANEO_API_KEY` |
| Workspace | `-w`, `--workspace` | `KANEO_WORKSPACE` |
| Project | `-p`, `--project` | `KANEO_PROJECT` |
| Stored login | `--profile` | `KANEO_PROFILE` |
| JSON output | `--json` | `KANEO_JSON=true` |
| Inline images | | `KANEO_IMAGES` (`kitty`, `iterm`, `blocks` or `off`) |
| Web app address for links | | `KANEO_WEB_URL` (detected at login) |
| Config file | | `KANEO_CONFIG` |

Logins live in `~/.config/kaneo/config.json` (or `$XDG_CONFIG_HOME/kaneo/config.json`), readable only by you. The running timer is tracked in `timer.json` next to it. If that file was written by a different kaneo CLI, this one leaves it untouched and asks you to move it or set `KANEO_CONFIG`.

`kaneo link` saves the workspace and project for a repository in a `.kaneo.json` at its root, and `kaneo unlink` removes them:

```json
{ "workspace": "your-workspace-id", "project": "KAN" }
```

The workspace comes from `-w`, then `KANEO_WORKSPACE`, then `.kaneo.json`, then the default from `kaneo workspace use`. The project comes from `-p`, then `KANEO_PROJECT`, then `.kaneo.json`. Run `kaneo context` to see which values are in use and where each came from.

## Develop

```bash
pnpm install
pnpm --filter @kaneo/cli build     # bundles to dist/kaneo.mjs
node packages/cli/dist/kaneo.mjs --help
pnpm --filter @kaneo/cli test
pnpm --filter @kaneo/cli typecheck
```

The CLI is written with [Effect](https://effect.website) 4 (`effect/cli`, `effect/http`, `effect/schema`) and bundled into one file with no runtime dependencies, so `kaneo --version` starts in about 50 ms. Commands are `Effect.fn` functions, failures are tagged errors mapped in one place (`src/errors/describe.ts`), and the API client, config store and terminal are services that tests replace with layers.

## Releasing

Bump `version` in `package.json` and merge to `main`. The `Release CLI` workflow (`.github/workflows/publish-cli.yml`) tests and bundles the CLI, compiles standalone binaries with Bun for macOS, Linux (glibc and musl) and Windows, and creates the `cli-v<version>` GitHub release with the binaries and a `SHA256SUMS` file. It skips versions that already have a release, and you can also run it by hand from the Actions tab.

After a stable release, the workflow also points the `kaneo` formula in [usekaneo/homebrew-tap](https://github.com/usekaneo/homebrew-tap) at the new binaries. It writes the formula with `scripts/homebrew-formula.sh` and pushes with the `HOMEBREW_TAP_TOKEN` secret, a fine-grained token that can write to that repository only.

While the `CLI_NPM_PUBLISH` repository variable is `true`, the same workflow also publishes `@kaneo/cli` to npm with provenance through trusted publishing, skipping versions that are already published.

---

### Overview (`packages/mcp/README.md`)

# Kaneo MCP server

[`@kaneo/mcp`](https://www.npmjs.com/package/@kaneo/mcp) is the official MCP (Model Context Protocol) server for [Kaneo](https://kaneo.app), the open source project management platform. It is maintained in the [usekaneo/kaneo](https://github.com/usekaneo/kaneo) monorepo and published to npm by the Kaneo team.

It runs over stdio, signs in with Kaneo's device flow, and then calls the Kaneo API with a bearer token. The package lives in `packages/mcp` in this monorepo and exposes the `kaneo-mcp` CLI.

> **Tip:** Every Kaneo instance also ships a built-in HTTP MCP endpoint at `/api/mcp`. If your MCP client supports Streamable HTTP transport (e.g. Claude Code), you can connect directly without this package. See the [MCP docs](https://kaneo.app/docs/core/integrations/mcp) for details.

## Prerequisites

- Node.js 24+
- A running Kaneo API (for example `http://localhost:1337`) and web app (for device approval UI).

Kaneo allows `kaneo-cli` and `kaneo-mcp` by default, so you usually do not need extra server configuration.

If you want to run this server with a different client ID, allow it on the Kaneo server:

```bash
DEVICE_AUTH_CLIENT_IDS=kaneo-cli,kaneo-mcp,your-client-id
```

## Environment

| Variable | Description |
|----------|-------------|
| `KANEO_API_URL` | Kaneo API origin (default `http://localhost:1337`). Do not include `/api`. |
| `KANEO_MCP_CLIENT_ID` | Device-flow client id (default `kaneo-mcp`). Must match `DEVICE_AUTH_CLIENT_IDS` on the server. |
| `KANEO_API_KEY` | **Optional.** A Kaneo API key (create one under Settings → Account → Developer). When set, the server authenticates with it as a Bearer token and skips the interactive device flow. Use this for headless/Docker setups. |

## Install

**Recommended (no global install):** run the interactive installer with npx:

```bash
npx @kaneo/mcp
```

npm downloads the package, then an **interactive menu** (arrow keys + Enter) asks **where** to register the server (Cursor user-wide, Cursor project, Claude Desktop, or a custom JSON path). It then merges a `mcpServers` entry that points at this package’s `dist/index.js` with your current Node binary.

In a normal terminal, `npx @kaneo/mcp` and `kaneo-mcp` with no subcommand both start the installer. When the process is **not** attached to a TTY (for example when Cursor launches the MCP server with a pipe), the same entry runs the stdio MCP server instead.

To run the server manually from a shell (for example to debug stdio), use:

```bash
npx @kaneo/mcp serve
```

If you prefer a global install:

```bash
npm install @kaneo/mcp
kaneo-mcp
```

(`kaneo-mcp install` is the same installer with an explicit subcommand.)

Non-interactive example (Cursor user config, skip overwrite prompts):

```bash
kaneo-mcp install --target cursor-user -y
```

Point at a self-hosted API when generating the config:

```bash
kaneo-mcp install --target cursor-user -y --api-url https://kaneo.example.com
```

See all options:

```bash
kaneo-mcp install --help
```

If you are currently inside the local `packages/mcp` package directory, npm may resolve the local workspace package instead of the published one and fail to expose the bin. In that case, either run `npx` from outside `packages/mcp`, or use a local build:

```bash
node dist/index.js
```

The published package includes `dist/`. `prepublishOnly` runs the build before publish.

## Develop from source

From the repo root:

```bash
pnpm install
pnpm --filter @kaneo/mcp run build
pnpm --filter @kaneo/mcp run start
pnpm --filter @kaneo/mcp run test
```

Or run it from the package directory:

```bash
pnpm -C packages/mcp run build
```

The CLI entry points to `./dist/index.js`. Use `npx @kaneo/mcp` or `kaneo-mcp` after a global install so your IDE config points at the resolved path.

## Authentication

On the first tool call that needs Kaneo, the server:

1. Requests a device code from `POST /api/auth/device/code`
2. Prints the verification URL and user code to `stderr`
3. Tries to open the browser
4. Polls `POST /api/auth/device/token` until approved
5. Stores the access token at `~/.config/kaneo-mcp/credentials.json` with mode `0600`

### Non-interactive (API key)

For headless or sandboxed environments where opening a browser is impractical, set `KANEO_API_KEY` to a key created under Settings → Account → Developer. The server sends it as a Bearer token on every request and skips the device flow entirely, so no token is cached to disk.

## Tools

- Session: `whoami`, `list_workspaces`, `list_workspace_members`, `list_notifications`
- Search: `search`
- Projects: `list_projects`, `get_project`, `create_project`, `update_project`, `list_project_columns`
- Tasks: `list_tasks`, `get_task`, `get_task_by_ticket_id`, `create_task`, `update_task`, `delete_task`, `move_task`, `update_task_status`, `update_task_assignee`, `update_task_due_date`, `list_task_activity`
- Custom fields: `list_project_custom_fields`, `get_task_custom_field_values`, `set_task_custom_field_value`
- Time entries: `list_task_time_entries`, `get_time_entry`, `create_time_entry`, `update_time_entry`
- Comments: `list_task_comments`, `create_task_comment`, `update_task_comment`, `delete_task_comment`
- Labels: `list_workspace_labels`, `create_label`, `attach_label_to_task`, `detach_label_from_task`, `delete_label`
- Task relations: `create_task_relation`, `get_task_relations`, `delete_task_relation`

Call `list_project_columns` before setting a status: the column slugs it
returns are the values `create_task` and `update_task_status` accept.
Call `get_task_by_ticket_id` with a key such as `KAN-12` to retrieve one task
without listing a project. If the same key identifies multiple accessible tasks,
pass `workspaceId` or `projectId` to select one.
`list_workspace_members` resolves the user IDs the assignee tools expect.
Time entries have no delete endpoint on the API, so there is no
`delete_time_entry` tool.
Call `list_project_custom_fields` for the field IDs and options
`set_task_custom_field_value` expects. Pass multiselect values as an array of
options, dates as `YYYY-MM-DD`, and `null` to clear a value.

## Releasing

Bump `version` in `packages/mcp/package.json` and merge to `main`. The [publish workflow](../../.github/workflows/publish-mcp.yml) runs the package tests, publishes the new version to npm, and creates a `mcp-v<version>` GitHub release. Nothing is published while the version stays the same, so tool changes reach npm only once the version is bumped.

Publishing a GitHub release manually also works: tag it `mcp-v<version>` with the tag version matching `packages/mcp/package.json` on the tagged commit.

---

### Overview (`packages/planka-import/README.md`)

# @kaneo/planka-import

Migrate your [PLANKA](https://planka.app) boards into [Kaneo](https://kaneo.app).

The CLI reads boards directly from PLANKA’s REST API and creates them through Kaneo’s public API. It does not modify PLANKA or use an intermediary migration service.

## Usage

```bash
npx @kaneo/planka-import --planka-url https://planka.example.com --dry-run
```

A dry run reads PLANKA only and prints exactly what would be created. When the
plan looks right, set `KANEO_API_KEY` in your environment and drop `--dry-run`:

```bash
npx @kaneo/planka-import \
  --planka-url https://planka.example.com \
  --kaneo-url https://cloud.kaneo.app \
  --workspace ws_123
```

You'll be prompted for your PLANKA login and for which boards to migrate.

Create a Kaneo API key under **Settings → Account → API Keys**. Self-hosting? Point
`--kaneo-url` at your own instance.

## What carries over

| PLANKA | Kaneo |
| --- | --- |
| Board | Project |
| List | Column (`Closed` lists become the final column) |
| Card | Task |
| Card description | Task description |
| Checklists (task lists) | Markdown checkboxes appended to the description |
| Checklist items linked to another card | Task relations (subtask) |
| Labels | Labels, with their original colors |
| Card members | Assignee, matched by email address |
| Due date | Due date |
| Comments | Comments, with the original author recorded separately and the original date added to the text |

One Kaneo project is created per PLANKA **board**, since the board is what holds
lists and cards. If a PLANKA project has several boards, each Kaneo project is
named `Project - Board`.

## What doesn't

- **Attachments.** Counted and reported, but not transferred.
- **Archive and trash lists.** Skipped by design; only active and closed lists
  are migrated.
- **Comment authorship.** The comment is created by the API key's owner, but the
  original PLANKA author is recorded and displayed alongside it. Requires Kaneo
  2.17.6 or newer.
- **Priorities.** The importer sets every task to
  `no-priority`.
- **Custom fields, stopwatches, and card subscriptions.** The importer does not transfer these. Kaneo supports custom fields and time entries, but you need to recreate that information yourself.

**Run the import as a PLANKA admin.** PLANKA hides other users' email addresses
from non-admin accounts, and assignees are matched by email, so a non-admin
import leaves every task unassigned.

Assignees also only match when the person already exists in the target Kaneo
workspace with the same email address. Invite your team first, then import, and
assignments come across on the first run.

## Options

| Flag | Description |
| --- | --- |
| `--planka-url <url>` | PLANKA instance URL (required) |
| `--planka-user <user>` | Email or username (prompted if omitted) |
| `--planka-password <pass>` | Password (or set `PLANKA_PASSWORD`) |
| `--planka-token <token>` | Use an existing access token instead of logging in |
| `--planka-api-key <key>` | Use a PLANKA API key (or `PLANKA_API_KEY`). Works for SSO-only accounts |
| `--kaneo-url <url>` | Kaneo instance URL (default `https://cloud.kaneo.app`) |
| `--kaneo-api-key <key>` | Kaneo API key (or set `KANEO_API_KEY`) |
| `--workspace <id>` | Target workspace (prompted if omitted) |
| `--project <name\|id>` | Migrate only this PLANKA project (repeatable) |
| `--all` | Migrate every board without prompting |
| `--dry-run` | Report what would happen, write nothing |
| `--skip-comments` | Don't migrate comments |
| `--icon <name>` | Lucide icon for created projects (default `Layout`) |
| `--report <path>` | Write a JSON report |
| `-y, --yes` | Skip the confirmation prompt |

If your PLANKA account uses SSO or has two-factor authentication enabled, there
is no password to log in with. Create a PLANKA API key for the account and pass
`--planka-api-key` instead.

## Re-running

The importer always creates new projects; it does not update ones it created
earlier. If an import goes wrong, delete the created Kaneo project and run it
again. Board-level failures are isolated, so one broken board won't stop the
rest, and the summary tells you which ones failed.

## License

MIT

---

### Overview (`scripts/ci/README.md`)

# CI checks

`ci.yml` runs on PRs and main, and is reused by Nightly. It checks formatting,
translations, OpenAPI, types, unit/integration tests, site links, workflow syntax,
real S3 uploads, and all three Dockerfiles. The bundled image is loaded and run
against disposable PostgreSQL and MinIO services for browser, realtime and
upgrade tests. Runtime failures retain container logs, a browser screenshot and
a Playwright trace for seven days.

The disposable S3 fixture pins Bitnami's archived MinIO 2025.7.23 image by its
multi-platform digest because the upstream public registries no longer serve
the previous image. It runs the MinIO binary directly with temporary test data.

- `browser.mjs`: real sign-in, project/task creation, status changes, another
  tab's realtime cache update, reload persistence and cross-workspace denial.
- `realtime.mjs`: authenticated clients receive the same mutation with no Redis,
  and when connected to separate API instances sharing Redis.
- `upgrade.sh` / `upgrade.mjs`: resolve the latest stable GitHub release to an
  image digest, seed it through public APIs, then replace it with the candidate
  image on the same database. Verify account credentials, owner membership,
  project/task fields, comments, private image bytes and subsequent writes.
- `require-ci.mjs`: release publication requires the latest CI run for the exact
  main SHA to succeed. It accepts push and manual CI runs, waits up to 30 minutes,
  and rejects failed, cancelled and skipped runs. If no push run exists (for
  example a release commit containing `[skip ci]`), dispatch **CI** on main before
  dispatching **Release**. Dry runs don't wait or publish.

The separate PR title check uses the base branch's commitlint policy and reruns
when the title changes. Helm validation also executes the existing secret and
upgrade-rendering regression checks. Configure the new job checks as required in
GitHub if they should block merging; editing workflows alone does not change
branch protection.

**Contribution eligibility** is a separate workflow. It posts a status on the
head commit of every open PR. Maintainers, vouched accounts, and approved bots
pass without an issue; everyone else has to link an open
`ready-for-contribution` issue from this repo. The workflow always takes its
code and policy from `main`, never from the PR. Voucher management and the
branch-rule setup are covered in
[the contribution guide](../../CONTRIBUTING.md#contribution-eligibility). Run
its tests with `node --test scripts/contribution-eligibility/*.test.mjs`.

## Local runtime checks

Use disposable services only. The HTTP helpers refuse non-loopback origins.
No root `.env`, production credentials or cloud storage are needed. You need
Docker, Node, pnpm, the API workspace dependencies and the existing browser
runner's dependencies (`npm ci --ignore-scripts --prefix scripts/ui-review-bot`).

After building a candidate image tagged `kaneo:ci`:

```sh
docker compose --env-file /dev/null -p kaneo-ci -f scripts/ci/compose.yml up -d --wait postgres minio app
node scripts/ci/browser.mjs http://127.0.0.1:55173
node scripts/ci/realtime.mjs http://127.0.0.1:55173
KANEO_CI_REDIS_URL=redis://redis:6379 docker compose --env-file /dev/null -p kaneo-ci -f scripts/ci/compose.yml --profile realtime up -d --wait app second redis
node scripts/ci/realtime.mjs http://127.0.0.1:55173 http://127.0.0.1:55174
GITHUB_REPOSITORY=usekaneo/kaneo GITHUB_REPOSITORY_OWNER=usekaneo bash scripts/ci/upgrade.sh
```

Install Chromium once using
`scripts/ui-review-bot/node_modules/.bin/playwright install chromium`.
Stop only this stack and remove its disposable data afterwards:

```sh
docker compose --env-file /dev/null -p kaneo-ci -f scripts/ci/compose.yml --profile realtime --profile upgrade down -v
```

The stack reserves loopback ports 55173–55175, 59040, 59379 and 59432. It uses
fixed synthetic credentials and test databases; do not expose it to the network.

For a local worktree on a copy-on-write filesystem, reuse the pnpm store without
copying dependency data:

```sh
pnpm install --offline --frozen-lockfile --ignore-scripts --package-import-method=clone
```

This preserves independent workspace links. The logical `node_modules` size
still includes shared blocks; it is not the additional physical disk cost.

---

### Overview (`scripts/slop-labeler/README.md`)

# Slop labeler

Adds `slop` when a PR adds or changes two distinct JS/TS comment blocks with
at least 20 words each and classifier scores of 99% or higher. Settings live in
[`policy.mjs`](./policy.mjs). Maintainer PRs are skipped.

The bot leaves one comment telling human authors they can ignore the result.
Later scans update that comment and remove the label if it no longer applies.
Manually added labels stay. Failed scans leave everything untouched.

Local tests found human false positives even at this threshold. A 99% score
doesn't mean 99% accuracy.

PR code is fetched as text and parsed, never executed. The workflow starts
running once merged into the default branch.

To scan an existing PR, run the workflow manually with its PR number.

Run the tests:

```sh
cd scripts/slop-labeler
npm ci --ignore-scripts
npm test
```

The classifier comes from [kqr's article](https://entropicthoughts.com/better-ai-comment-classifier).
`vendor/` preserves the [upstream source](https://git.sr.ht/~kqr/aicomment) at
`9d3abed60e628951c7b0d2cd1e1c69eddb23eb97`, the
[published model](https://xkqr.org/aicomment/model.json), and its license.
Tests check the model and inference hashes. Recheck thresholds before replacing
the model.

---

### Overview (`scripts/ui-review-bot/README.md`)

# Peekareq

A maintainer comments `/peekareq` on an open Kaneo PR. The bot selects up to three UI scenarios, captures the PR in Chromium, frames the changed component, uploads three distinct previews, and posts this line followed by a description/image table:

> This UI-screenshot was auto-made by a beta tool made by @tinsever

Below the screenshot table, **Findings (Beta)** and **Run info** are collapsed by default. Findings contain axe-core accessibility results and clearly labeled AI suggestions with practical fixes. Run info lists the models that returned responses, provider-reported AI cost, and elapsed time from planning through review (including job waits). Re-runs update the bot's existing comment.

## GitHub setup

- Install the GitHub App on `usekaneo/kaneo` with **Contents: Read and write** and **Pull requests: Read and write**, and **Issues: Read-only**. The installation owner must approve permission updates.
- Set Actions variable `APP_CLIENT_ID` and secrets `APP_PRIVATE_KEY` (the complete PEM file) and `OPENROUTER_API_KEY`.
- Put this directory and both `ui-review.yml` and `ui-review-run.yml` workflows on the default branch.
- Deploy `worker/` to Cloudflare (see below). Enable the App webhook at its `/webhook` URL, set the same signing secret, and subscribe only to **Issue comment**.

Comment exactly `/peekareq` in a PR's conversation, or use **Actions → Peekareq → Run workflow**. Current write, maintain, or admin access is required. Bots, comment edits, quoted commands, arguments, ordinary issues, and closed PRs do not start a run. Failed permission checks stop the request before model calls. Only authorized requests can cancel an earlier run for the same PR.

The final job authenticates as `<app-slug>[bot]`. PNGs are stored on a dedicated `code/ui-screenshots` branch and embedded using immutable URLs. Concurrent uploads preserve earlier images. Failed captures and reports for a changed or closed PR are not posted.

## Local use

Requires Node 24, pnpm, git, and authenticated `gh` with contents and PR-comment write access. Workflows use Node 24.

```sh
npm --prefix scripts/ui-review-bot ci --ignore-scripts
npm --prefix scripts/ui-review-bot run setup
npm --prefix scripts/ui-review-bot start -- 1719
```

A PR number or URL is required. Runs upload and comment automatically; add `--no-post` to keep results local. The default key file is `~/.env.openroutercopythis`, containing `API_KEY=…` or `OPENROUTER_API_KEY=…`. It is parsed as data, never executed.

```sh
npm --prefix scripts/ui-review-bot start -- 1719 --no-post
npm --prefix scripts/ui-review-bot start -- 1719 --capture-only --no-post
npm --prefix scripts/ui-review-bot test
node scripts/ui-review-bot/publish.mjs /absolute/path/to/report.json
```

Use `--model provider/model` or `--key-file /path/to/key.env` to override defaults. Local App authentication uses `GH_TOKEN` and `UI_REVIEW_BOT_LOGIN=<app-slug>[bot]`; otherwise `gh` supplies your user identity.

Default model: `qwen/qwen3.8-flash`, verified against OpenRouter's live vision-model catalog. If its provider is rate-limited or temporarily unavailable, the run switches to `google/gemini-2.5-flash-lite`; explicitly selected other models keep their selection. Temporary failures retry at most twice and all attempts count toward the five-request budget. The model receives the PR diff, relevant source, synthetic page text, and screenshots. Each run allows at most five calls, three scenarios, and six interactions per scenario. Reports record provider-reported usage and cost; there is no fixed dollar cap.

Local reports, before/after PNGs, and pixel differences are saved in `.cache/ui-review-bot/runs/<id>/`. Ctrl+C cancels capture and closes previews.

## Coverage and isolation

The current fixture adapter supports account settings, a synthetic workspace/project/task, stateful time-entry Start/Stop controls, and custom-field values. Multiselect changes use fixture-backed scenarios for project configuration, the option picker, and multiple selected values. Each scenario identifies the component to frame and controls that must be visible; portalled menus are included in the frame. Missing or clipped controls and identical previews prevent publication. Defined fixture scenarios use their short, specific names as captions. Marketing-site and docs-only changes stop during planning because the preview currently runs `apps/web` only. Each scenario gets its own fixture state. The capture-only preset is specific to PR #1719. Other features may require fixtures; unknown API reads produce incomplete captures. Full before/after captures use a 1440×1000 desktop viewport; GitHub receives bounded close-up previews captured directly in Chromium. Captions are limited to 70 characters. Captures use light theme, English, UTC, and disabled animations. The captured PR areas are audited with axe-core 4.13.0 using WCAG 2 A/AA, 2.1 A/AA, 2.2 AA, and best-practice rules. Confirmed violations and checks needing manual review remain distinct. AI also reviews visible clipping, overlap, readability, and interaction feedback. These checks do not establish full accessibility compliance or test real backend behavior.

Before images use the PR's merge base. Different routes or interaction states are labeled in the local report rather than treated as regression scores. Each revision uses its own frozen lockfile with install scripts disabled. Browser traffic is restricted to the local preview and synthetic API responses.

The Worker verifies GitHub signatures and current maintainer access before sending `repository_dispatch`. Ordinary comments never create Actions entries. Actions rechecks the original comment and author before planning. Authorization, planning, capture, and review/posting use separate GitHub-hosted runners. Only capture executes PR code, without provider secrets or a write token. Trusted jobs check out the workflow revision. Review accepts bounded capture JSON and PNGs, preserves the trusted plan's PR identity, rejects malformed images, and recalculates differences. App credentials are provided only after review. The default Actions token is read-only.

Local capture executes PR code as your OS user; it is not an OS sandbox. Use GitHub-hosted capture for untrusted PRs. Credentials are not passed into preview environments or written to reports. PR previews, short captions, bounded findings, and run statistics are published; intermediate workflow artifacts expire after one day.

## Cloudflare webhook

The Worker is `peekareq-webhook`; endpoint: `https://peekareq-webhook.marmeladenjunge.workers.dev/webhook`. It uses the Workers Free-compatible request path and a small D1 table to deduplicate command comment IDs. The optional code-review relay also uses this Worker; browser captures continue to run in Actions. Its installation token is restricted to `kaneo` with Contents write and Pull requests read; repository dispatch uses the existing Contents permission.

```sh
npx wrangler@4.133.0 d1 execute peekareq-commands --remote --file scripts/ui-review-bot/worker/schema.sql --config scripts/ui-review-bot/worker/wrangler.jsonc
npx wrangler@4.133.0 secret put APP_PRIVATE_KEY --config scripts/ui-review-bot/worker/wrangler.jsonc
npx wrangler@4.133.0 secret put WEBHOOK_SECRET --config scripts/ui-review-bot/worker/wrangler.jsonc
npx wrangler@4.133.0 deploy --config scripts/ui-review-bot/worker/wrangler.jsonc
```

Store the App key as PKCS#8 PEM (convert a downloaded PKCS#1 key with `openssl pkcs8 -topk8 -nocrypt`). Keep secrets out of source control. For a new account, create a D1 database and update its ID in `wrangler.jsonc`. The Worker checks the configured installation ID and repository before using App credentials. If dispatch times out after claiming a command, it deliberately does not replay it: check Actions first, then post a fresh command if needed.

## Code review (Beta)

`/peekareview` requests a source-based review of an open PR. It uses the same live maintainer authorization and webhook filtering as `/peekareq`. Ordinary comments do not start either workflow. Re-runs update one Peekareq comment containing collapsed **Findings (Beta)** and **Run info**, including model, new AI cost, duration, and reviewed revision.

The code reviewer uses `deepseek/deepseek-v4-flash` through OpenRouter. It reads immutable base/head source without checking out or executing PR code. It retrieves callers, guards, constraints, and related tests; a second pass challenges each candidate, then a base-only pass checks that the behavior is newly introduced. Findings need validated source citations. Missing evidence, malformed responses, timeouts, and omitted files produce explicitly incomplete results. An empty review is not an approval.

Deployment requires `code-review.yml`, the updated Worker/schema, Worker secrets `OPENROUTER_API_KEY` and `REVIEW_PROXY_TOKEN`, Actions secret `PEEKAREVIEW_PROXY_TOKEN` with the same 64-character hexadecimal token, and Actions variable `PEEKAREVIEW_MODEL_URL` pointing to the Worker's `/review-model` endpoint. The provider key stays in the Worker for code reviews. The existing screenshot workflow keeps its separate provider-key configuration.

The relay requires an initialized `review_budget` row with `id = 1`, `ceiling = 850000`, and `initial_spend` equal to the already committed trial spending in integer microdollars. It refuses inference without initialization. The $0.85 trial ceiling includes prior development spend and a 15% allowance; it never resets automatically. Requests reserve maximum cost atomically before inference, and unknown charges remain reserved. Successful identical requests are cached; unresolved requests are not automatically retried within a run. An explicitly requested new run can try again while retaining the earlier reservation. A local review also has a $0.10 limit and a 12-minute deadline.

For a private local review:

```sh
node scripts/ui-review-bot/code-review/cli.mjs 1735
node scripts/ui-review-bot/code-review/cli.mjs --base BASE_SHA --head HEAD_SHA
node scripts/ui-review-bot/code-review/cli.mjs --budget
```

Reports are saved under `.cache/code-review/`; the local CLI does not post to GitHub. The Actions publisher rechecks the original command, current maintainer access, PR head, and base immediately before posting.

---

### Overview (`tests/api-integration/README.md`)

# API integration tests

These tests boot the Hono app with a real PostgreSQL database (see `setup.ts` for env defaults).

- Run: `pnpm test:integration` from the repo root (requires Postgres and `DATABASE_URL`).
- CI: `.github/workflows/ci.yml` starts Postgres and runs the same command.

Coverage is intentionally incremental: add new files under this directory for additional routes or behaviors, following existing helpers (`helpers/fixtures.ts`, `helpers/database.ts`, `helpers/auth.ts`).

---

### Overview (`tests/e2e/README.md`)

# Browser smoke tests

These Playwright tests run against a disposable, bundled Kaneo instance built
from the current checkout. They exercise real authentication, workspace creation,
persistence after a reload, and authentication after clearing the session cookie.
They also create projects and tasks, persist task edits, and verify that another
workspace member receives edits through a real WebSocket without reloading.
PostgreSQL uses temporary storage; no existing database or root `.env` is used.

## Local run

Requires Node 24, pnpm 10.32.1, and Docker Compose v2.

```sh
pnpm --dir tests/e2e install --frozen-lockfile --ignore-scripts
pnpm --dir tests/e2e exec playwright install chromium
docker compose -f tests/e2e/compose.yml up --build --wait --wait-timeout 180
pnpm test:e2e
docker compose -f tests/e2e/compose.yml down --volumes
```

The test instance binds to `http://localhost:18173`. Tests create unique synthetic
users and workspaces. Stop the Compose stack after testing, including failed runs;
its temporary database is discarded. The stack's project name is
`kaneo-browserstack-e2e`; do not run two copies simultaneously on the same machine.
Reports, failure screenshots, and local Playwright traces are stored in `.cache/e2e/`.
BrowserStack disables native Playwright tracing; use its session recordings for cloud runs.

## BrowserStack Automate

Set `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` in your shell's environment
using the credentials from your BrowserStack account. Keep them out of source
control and command-line arguments. Start the Compose stack with BrowserStack's
local hostname and the HTTPS override so the app, API, cookies, and WebSockets
share the same secure origin. This also requires OpenSSL:

```sh
export KANEO_E2E_HOST=bs-local.com
export KANEO_E2E_TLS=true
export COMPOSE_FILE=tests/e2e/compose.yml:tests/e2e/compose.tls.yml
mkdir -p .cache/e2e-tls
openssl req -x509 -newkey rsa:2048 -nodes -days 1 \
  -keyout .cache/e2e-tls/key.pem -out .cache/e2e-tls/cert.pem \
  -subj '/CN=bs-local.com' -addext 'subjectAltName=DNS:bs-local.com'
docker compose up --build --wait --wait-timeout 180
pnpm test:browserstack
docker compose down --volumes
unset KANEO_E2E_HOST KANEO_E2E_TLS COMPOSE_FILE
```

BrowserStack rewrites `localhost` to `bs-local.com` for WebKit. Configuring that
hostname explicitly avoids mixing the page's origin with a localhost API URL.
The cloud endpoint is `https://bs-local.com:18174`. The test-only nginx gateway
uses a freshly generated, one-day self-signed certificate; the cloud test contexts
accept it. Certificate files stay outside uploaded reports. HTTPS avoids the HTTP
400 WebSocket handshake failures observed with WebKit through the Local tunnel.
Local Chromium PR runs continue to exercise the plain HTTP deployment.

The realtime test waits for the application's own keepalive before editing, then
asserts both the incoming task event and the updated board without reloading.

The SDK starts and stops BrowserStack Local to connect remote browsers to the
disposable instance. `tests/e2e/browserstack.yml` runs Chrome on Windows 11 and Playwright
WebKit on macOS Sequoia, with one session per platform (two concurrent sessions).
WebKit is an engine compatibility check; this configuration does not test Safari
on a real iPhone. Remove a platform if your plan allows only one parallel session.
Playwright is pinned to 1.62.1, from BrowserStack's supported 1.62 version family;
check their supported versions before upgrading it.

This integration uses Automate only. Test Reporting & Analytics is disabled;
Percy and other BrowserStack products are not configured. Session results and
videos appear in the Automate dashboard under **Kaneo**.

## GitHub Actions

1. Add repository Actions secrets `BROWSERSTACK_USERNAME` and
   `BROWSERSTACK_ACCESS_KEY`.
2. Merge this integration into `main`.
3. Every push to `main` now runs **BrowserStack** automatically. To rerun it,
   open **Actions → BrowserStack → Run workflow**, selecting `main`.

**Browser tests** runs the same suite in Chromium for every pull request, including
forks, without cloud credentials. **BrowserStack** runs Chrome on Windows and
WebKit on macOS after a push to `main`, or a manual dispatch on `main`. Cloud runs
are serialized to stay within the two-session limit; obsolete PR runs are cancelled.

Both workflows build the checked-out revision, use a disposable database, upload
reports for seven days, and tear down the stack even after failure. BrowserStack
credentials are passed only to its credential check and cloud test step. No
production instance is contacted. Tests use synthetic accounts; invitation setup
uses the normal API without sending email because SMTP is not configured.

Add the **Chromium smoke tests** check to branch protection when it has proven
stable. The cloud check runs after merge and does not currently gate releases.

References:

- [BrowserStack Playwright SDK setup](https://www.browserstack.com/docs/automate/playwright/getting-started/nodejs/integrate-your-tests)
- [BrowserStack Local](https://www.browserstack.com/docs/automate/playwright/getting-started/nodejs/local-testing)
- [Supported Playwright versions and platforms](https://www.browserstack.com/docs/automate/playwright/browsers-and-os)

---

### Overview (`tests/storage-integration/README.md`)

# Local storage contract tests

These tests use a disposable S3-compatible service on `127.0.0.1` with the fixed, non-production credentials `local-test-access` / `local-test-secret-only`. They refuse remote endpoints, create a unique test bucket and delete only that bucket and its test objects afterwards. Never point them at an existing storage service.

Start a local MinIO test container bound to loopback with those root credentials:

```sh
docker run --rm -d --name kaneo-storage-contract-test \
  -p 127.0.0.1:59039:9000 \
  -e MINIO_ROOT_USER=local-test-access \
  -e MINIO_ROOT_PASSWORD=local-test-secret-only \
  -e MINIO_BROWSER=off -e MINIO_UPDATE=off \
  --entrypoint /opt/bitnami/minio/bin/minio \
  bitnamilegacy/minio@sha256:8935e75fa5d11295c17171e4aa49efe390a1193cd7f12e4d21b92af9ffef09d7 \
  server /tmp/minio --address :9000
```

Once the service is ready, run:

```sh
KANEO_STORAGE_TEST_ENDPOINT=http://127.0.0.1:59039 pnpm --filter @kaneo/api exec vitest run --config vitest.storage.config.ts
docker stop kaneo-storage-contract-test
```

The suite checks signed size/content-type enforcement, chunked-transfer rejection, actual object metadata, missing objects and oversized-object cleanup. The regular API integration suite separately covers endpoint authorization and database writes with mocked storage calls. No real cloud account or email provider is required.

---

### Overview (`apps/docs/images/product/README.md`)

# Documentation screenshots

These are captures of the real Kaneo frontend using fictional Studio North data. No live account or database is used.

Board, List, task, and Gantt images use the same fixture capture as `apps/site/public/images/product/`, in both light and dark themes. Their original viewport is 1728 × 1000 at 2× density. These eight images are checked-in captures; the settings capture script below does not regenerate them.

Backlog, members, labels, workflow, integrations, notifications, and security settings are captured at 1440 × 1000 with 2× density. From the repository root, run:

```bash
node scripts/ui-review-bot/docs-screenshots.mjs
```

The script requires the UI review bot's Playwright dependencies and Chromium. It starts its own Vite server on port 5188, intercepts API calls with fixtures, blocks external requests, checks for browser errors and missing fixtures, and stops its server when finished.

Place screenshots in a Mintlify `Frame`, with `block dark:hidden` on the light image and `hidden dark:block` on the dark image. Include alt text and a caption. Review each changed capture before publishing.

The current documentation captures were refreshed against the local frontend on 2026-09-26. The fixture date controls sample task deadlines, not the capture date.

To capture only one view, set `DOCS_SCREENSHOT_VIEW`, for example `DOCS_SCREENSHOT_VIEW=integrations node scripts/ui-review-bot/docs-screenshots.mjs`.

---

### Overview (`apps/site/public/cursors/README.md`)

Hand cursors from https://cursor.in/ by Sawyer Hollenshead.
Source: https://github.com/sawyerh/cursor.in

The SVG viewBoxes are cropped to remove transparent margins for the product tour.

---

### Apps Docs Index (`apps/docs/index.mdx`)

---
title: "Kaneo docs"
description: "Get your team started, find your way around, or run Kaneo on your own server."
---

Keep your projects, tasks, and conversations in one place. Start with a small project and add the setup your team needs as you go.

<Frame caption="A website launch in Board view. Each column shows a stage of the work.">
  <img className="block dark:hidden" src="/images/product/board-light.png" alt="A website launch in Board view. Each column shows a stage of the work." />
  <img className="hidden dark:block" src="/images/product/board-dark.png" alt="A website launch in Board view. Each column shows a stage of the work." />
</Frame>

<CardGroup cols={2}>
  <Card title="Your first project" icon="rocket" href="/core/index">
    Create a workspace, invite a teammate, and finish your first task.
  </Card>
  <Card title="Run your own instance" icon="server" href="/core/installation/index">
    Install Kaneo with Docker Compose, drim, or your hosting platform.
  </Card>
  <Card title="Connect your tools" icon="plug" href="/core/integrations/index">
    Sync repositories, send project updates, and connect an AI assistant.
  </Card>
  <Card title="Build with the API" icon="code" href="/api-reference/introduction">
    Make your first request and explore the endpoint reference.
  </Card>
</CardGroup>

## How Kaneo is organized

A **workspace** holds your team, projects, and shared labels. A **project** holds a body of work, such as a website launch. A **task** is one piece of that work, with its own status, assignee, description, and conversation.

If someone invited you, open their invitation link and sign in with the email address they invited. You can join their workspace without creating a new one.

## Looking for something specific?

- [Board and List](/core/functional/plan-and-execute-tasks): create, find, and update tasks.
- [Members and roles](/core/functional/collaborate-with-team): invite people and manage access.
- [Backups and recovery](/core/operations/backups): protect a self-hosted installation.
- [Troubleshooting](/core/operations/troubleshooting): work through common setup problems.

Screenshots throughout these guides show the real app with a fictional team, Studio North.

---

### Apps Docs Cli Index (`apps/docs/cli/index.mdx`)

---
title: Kaneo CLI
sidebarTitle: Overview
description: Manage tasks and projects from your terminal with the official Kaneo command-line client.
---

The official Kaneo CLI is a single `kaneo` binary for macOS, Linux and Windows. It works with Kaneo Cloud and self-hosted instances, shows styled output in a terminal, and prints plain JSON for scripts and agents.

## Quick start

```bash
curl -fsSL https://kaneo.app/cli/install.sh | sh
kaneo login
kaneo
```

On Windows, install it in PowerShell with `irm https://kaneo.app/cli/install.ps1 | iex`. Homebrew (`brew install usekaneo/tap/kaneo`) and npm (`npm install @kaneo/cli`, Node.js 20 or newer) work too. [Install](/cli/install) covers versions, mirrors and supported systems, and [Sign in](/cli/sign-in) covers self-hosted servers and API keys.

## Everyday commands

```bash
kaneo workspace list
kaneo workspace use            # pick the default workspace
kaneo project list
kaneo task list -p KAN         # tasks grouped by column
kaneo task list -p KAN --mine --open
kaneo task mine                # your open tasks, soonest due first
kaneo task view KAN-12
kaneo task create "Fix login redirect" -p KAN --priority high --due tomorrow
kaneo task status KAN-12 done
kaneo task assign KAN-12 me
kaneo comment add KAN-12 "Shipped in v2.35"
kaneo time start KAN-12
kaneo time stop
kaneo task open KAN-12
kaneo search "login redirect"
```

Tasks are addressed by their ticket ID, such as `KAN-12`, or by their ID. If a ticket ID exists in more than one of your workspaces, pass `-w` to choose one.

When a workspace, project or column is missing and you are in a terminal, the CLI shows a searchable picker. In scripts it stops with an error that names the flag to pass.

Every command has `--help`, and the [command reference](/cli/reference) lists every argument and flag. Print a shell completion script with `kaneo --completions zsh` (or `bash`, `fish`, `sh`).

## Update

Run the install command again. It replaces `kaneo` with the latest release and keeps your logins and settings. See [Install](/cli/install#update) for pinned versions and mirrors.

## Next steps

<CardGroup cols={2}>
  <Card title="Install" icon="download" href="/cli/install">
    Pin a version, use a mirror, update and uninstall.
  </Card>
  <Card title="Sign in" icon="key-round" href="/cli/sign-in">
    Self-hosted servers, API keys for CI, and several logins.
  </Card>
  <Card title="Configuration" icon="settings" href="/cli/configuration">
    Environment variables, repository defaults and inline images.
  </Card>
  <Card title="Scripts and agents" icon="braces" href="/cli/scripting">
    JSON output, jq filters, raw API calls and the agent skill.
  </Card>
  <Card title="Command reference" icon="book-open" href="/cli/reference">
    Every command with its arguments and flags.
  </Card>
  <Card title="MCP server" icon="bot" href="/core/integrations/mcp">
    Connect AI assistants such as Claude and Cursor without a shell.
  </Card>
</CardGroup>

---

### Apps Docs Core Index (`apps/docs/core/index.mdx`)

---
title: "Your first project"
description: "From an empty workspace to a finished task."
---

You need a Kaneo account and access to an instance. Use [Kaneo Cloud](https://cloud.kaneo.app), join your team's instance, or [host your own](/core/installation/index).

We'll use a website launch as an example. You can follow along with your own project name.

## 1. Create a workspace

After signing in, create a workspace for your team, for example **Studio North**. If you already belong to a workspace, use the workspace menu at the top of the sidebar to switch to it.

A workspace is the boundary for membership and permissions. Separate teams that should not share access belong in separate workspaces.

## 2. Add a project

<Tip>
  Moving from Linear, Jira, Trello, Plane, or PLANKA? [Import your projects](/core/migrations) instead of starting from scratch.
</Tip>

Choose **Add project** in the sidebar and create **Website launch**. Open the project, then choose **Tasks**. Board and List are two views of the same tasks.

<Frame caption="Website launch, with work grouped into To Do, In Progress, In Review, and Done.">
  <img className="block dark:hidden" src="/images/product/board-light.png" alt="Website launch, with work grouped into To Do, In Progress, In Review, and Done." />
  <img className="hidden dark:block" src="/images/product/board-dark.png" alt="Website launch, with work grouped into To Do, In Progress, In Review, and Done." />
</Frame>

Start with the existing columns. You can [change the workflow](/core/functional/configure-workflows) later.

## 3. Write the first task

Use the **+** button in a board column to add a task. Call it **Build the new homepage**, then open it.

Add a short description of what needs to be true when the task is finished. For example:

> Publish the new homepage with approved copy, a working contact link, and a layout checked on mobile.

Use a checklist for the smaller steps. Set a due date if there is a real deadline, and add a label such as **Design** if it will help you find related work.

<Frame caption="An open task with a description, checklist, assignee, dates, labels, and comments.">
  <img className="block dark:hidden" src="/images/product/task-light.png" alt="An open task with a description, checklist, assignee, dates, labels, and comments." />
  <img className="hidden dark:block" src="/images/product/task-dark.png" alt="An open task with a description, checklist, assignee, dates, labels, and comments." />
</Frame>

## 4. Invite a teammate

Open **Members** in the workspace sidebar and choose **Invite member**. Enter your teammate's email address, then copy the invitation link and share it with them. If your instance has email configured, Kaneo can also deliver the invitation by email.

Your teammate opens the link, signs in with the invited email address, and accepts. Return to the task and choose an assignee. If that control is unavailable, ask a workspace admin to check your role.

See [Members and roles](/core/functional/collaborate-with-team) for pending invitations and access changes.

## 5. Move the work forward

Drag the task into **In Progress** when work starts. Add questions and updates in the task's comments so the context stays with the work.

When the work is finished, move it to the project's done column. If it needs another pass, move it back to an active column.

You now have a shared project with a task your team can follow from start to finish.

<CardGroup cols={2}>
  <Card title="Work with tasks" icon="square-check" href="/core/functional/plan-and-execute-tasks">Explore Board, List, filters, and task details.</Card>
  <Card title="Plan upcoming work" icon="list" href="/core/functional/backlog-planning">Keep ideas in the backlog until they are ready.</Card>
</CardGroup>

---

### Apps Docs Core Functional Index (`apps/docs/core/functional/index.mdx`)

---
title: "Using Kaneo"
description: "Find the guide for what you want to do next."
---

New to Kaneo? Start with [Your first project](/core/index).

| You want to… | Read |
| --- | --- |
| Set up a team or another project | [Workspaces and projects](/core/functional/create-workspace-and-project) |
| Create, assign, or find a task | [Board, List, and task details](/core/functional/plan-and-execute-tasks) |
| Prepare work for later | [Backlog](/core/functional/backlog-planning) |
| See work over time | [Calendar and Gantt](/core/functional/calendar-and-gantt) |
| Invite someone or change their access | [Members and roles](/core/functional/collaborate-with-team) |
| Change project columns | [Workflows](/core/functional/configure-workflows) |
| Organize related tasks | [Labels](/core/functional/manage-workspace-labels) |
| Choose which updates you receive | [Notifications](/core/functional/account-notifications) |
| Change the theme or task display | [Preferences](/core/functional/personalize-your-workspace) |
| Update your profile or delete your account | [Your account](/core/functional/manage-your-account) |

---

### Apps Docs Core Installation Index (`apps/docs/core/installation/index.mdx`)

---
title: "Self-host Kaneo"
description: "Choose a deployment method and know what you need to run it."
---

Kaneo runs as one application container plus PostgreSQL. You can add file storage and email when you need them. Redis is only needed when multiple API instances must share live updates.

## Choose your setup

| Setup | Choose this when… |
| --- | --- |
| [Docker Compose](/core/installation/docker-compose) | You want to see and control the configuration. Start here for a local trial or an existing server. |
| [drim](/core/installation/drim) | You want a CLI to generate the deployment and set up Caddy for HTTPS. |
| [Coolify](/core/deployments/coolify) | You already deploy applications through Coolify. |
| [Railway](/core/deployments/railway) | You want a hosted deployment without managing a server. |
| [Helm](https://github.com/usekaneo/kaneo/tree/main/charts/kaneo) | You already operate a Kubernetes cluster. |

For a small installation, plan for at least 2 GB of memory and 10 GB of free disk as a starting point. Leave additional space for database growth, uploaded files, and backups. Compose deployments need Docker and Docker Compose V2.

## What runs where

```mermaid
flowchart LR
  Browser[Your browser] -->|HTTPS| Proxy[Reverse proxy]
  Proxy --> Kaneo[Kaneo: web app and API]
  Kaneo --> DB[(PostgreSQL)]
  Browser -->|File uploads| Storage[(Optional S3 storage)]
  Kaneo -->|Read and validate files| Storage
```

PostgreSQL stores accounts, projects, tasks, comments, and upload records. File contents live in your configured object storage. Account avatars are stored in PostgreSQL.

## Before inviting your team

1. Put the instance behind [HTTPS](/core/reverse-proxy/nginx).
2. Create the initial administrator account before opening registration to others.
3. Configure [email and access settings](/core/installation/environment-variables).
4. Add [Silo file storage](/core/installation/silo) if your team needs attachments.
5. Make a [backup and test a restore](/core/operations/backups).

Then follow [Your first project](/core/index).

---

### Apps Docs Core Integrations Index (`apps/docs/core/integrations/index.mdx`)

---
title: Integrations
description: Find the right connection for repositories, team updates, personal notifications, and automation.
---

Connect the tools your team already uses. Repository connections and shared notifications belong to a project. Personal notification channels belong to your account.

## Repositories

<CardGroup cols={3}>
  <Card title="GitHub" icon="github" href="/core/integrations/github/setup">
    Sync issues and comments, link pull requests, and automate task status.
  </Card>
  <Card title="Gitea" icon="git-branch" href="/core/integrations/gitea/setup">
    Connect your Gitea server for issue sync and workflow automation.
  </Card>
  <Card title="GitLab" icon="gitlab" href="/core/integrations/gitlab/setup">
    Connect GitLab.com or your own instance to sync issues and track merge requests.
  </Card>
</CardGroup>

GitHub, Gitea, and GitLab have connection forms under **Settings → Projects → your project → Integrations**. Follow the provider guide to connect a repository and configure its webhook.

## Limit repository sync by label

Enable **Advanced settings** in **Account → Preferences** or **Workspace → General**, then open a connected GitHub, Gitea, or GitLab integration. Workspace settings managers can edit its **Sync scope**. The saved rules apply to everyone in the project; turning advanced mode off only hides the editor.

- **Kaneo → repository** selects workspace labels by stable ID. Match any selected label or require all of them. The preview shows matching tasks, new exports, and affected links before you apply the rule.
- **Repository → Kaneo** selects exact repository label names for new tasks and imports. Existing linked tasks follow the Kaneo rule. Adding a qualifying repository label can admit an issue that was previously excluded.

When a linked task loses its qualifying labels, synchronization pauses in both directions. Both records and their link remain. Reapplying the label does not overwrite either side: choose **Review and resume**, compare the current title, description, and open/closed state, then keep the Kaneo or repository values. Historical comments are not replayed. Missing or deleted workspace labels keep the filter closed until you repair the rule.

Resuming leaves priority, detailed status, and custom labels unchanged. Later repository label events apply changes made after the comparison, so an unrelated label event does not restore stale values from the pause. Issue filters do not prevent branch or pull request linking and their configured status transitions.

Kaneo rereads the issue when you resume and rejects a changed comparison. Local edits remain available while the repository responds; a task, rule, label, or link change during a resume write keeps synchronization paused for another review. When adopting a different open/closed state, resume uses the project's configured issue close or reopen workflow target. Gitea's content version, when available, also protects the body write from concurrent edits. Provider APIs do not offer an atomic version check for every issue field, so an edit made after the final read can still conflict with a resume write. Avoid editing the repository issue while resuming it. A rejected provider write leaves synchronization paused for another review.

Kaneo checks the repository again after either resume choice, verifying the selected title, description, and open/closed state along with the original labels. A mismatch or failed verification pauses the link for another review, leaving priority and custom labels unchanged. Adopted values remain visible in the next comparison if verification fails.

An issue creation request already sent to a repository may finish after its task stops matching. Kaneo keeps the returned issue link paused and stops further updates. Local edits remain available while the repository responds.

If creating an issue succeeds but its initial state, labels, or linking comment fails, **Export matching tasks** retries the unfinished steps using the existing issue link.

Newly matching tasks without an issue link are exported in the background when rules or task labels change. Use **Export matching tasks** to retry tasks that still have no issue link after a failed export. An inactive integration does not export or resume tasks. Existing installations retain unrestricted sync until a rule is saved.

## Team notifications

Send selected project events to a shared channel. Open **Settings → Projects → your project → Integrations** to connect these services.

<CardGroup cols={2}>
  <Card title="Slack" icon="slack" href="/core/integrations/slack">
    Post task updates to a Slack channel through an incoming webhook.
  </Card>
  <Card title="Discord" icon="message-circle" href="/core/integrations/discord">
    Send task activity to a Discord channel.
  </Card>
  <Card title="Mattermost" icon="messages-square" href="/core/integrations/mattermost">
    Connect an incoming webhook on your Mattermost instance.
  </Card>
  <Card title="Telegram" icon="send" href="/core/integrations/telegram">
    Send updates through a bot to a chat, group, channel, or forum topic.
  </Card>
</CardGroup>

<Frame caption="Project integrations live together in Settings. Expand a service to configure its connection.">
  <img className="block dark:hidden" src="/images/product/integrations-light.png" alt="Kaneo project settings showing the GitHub, Gitea, GitLab, and team notification integrations." />
  <img className="hidden dark:block" src="/images/product/integrations-dark.png" alt="Kaneo project settings showing the GitHub, Gitea, GitLab, and team notification integrations." />
</Frame>

## Personal notifications

Receive updates meant for you, such as assignments, mentions, and due-date reminders. Connect these under **Settings → Account → Notifications**, then choose the workspaces and projects that can send them.

<CardGroup cols={2}>
  <Card title="ntfy" icon="bell" href="/core/integrations/ntfy">
    Publish your notifications to an ntfy topic.
  </Card>
  <Card title="Gotify" icon="smartphone" href="/core/integrations/gotify">
    Receive notifications through your Gotify server.
  </Card>
  <Card title="Email" icon="mail" href="/core/functional/account-notifications#2-connect-a-channel">
    Send updates to your account email. Self-hosted instances need SMTP.
  </Card>
  <Card title="Personal webhook" icon="webhook" href="/core/integrations/personal-webhooks">
    Forward your own notifications to an HTTP endpoint.
  </Card>
</CardGroup>

[Notification preferences](/core/functional/account-notifications) control the events and workspace rules for all four channels.

## Automation and AI assistants

<CardGroup cols={3}>
  <Card title="Project webhooks" icon="radio" href="/core/integrations/outgoing-webhooks">
    Send selected project events to your service as signed JSON requests.
  </Card>
  <Card title="MCP" icon="bot" href="/core/integrations/mcp">
    Let an MCP client work with your Kaneo tasks and projects.
  </Card>
  <Card title="REST API" icon="code" href="/api-reference/introduction">
    Create tasks, query projects, or build a connection to another service.
  </Card>
</CardGroup>

## Looking for sign-in or file storage?

Sign-in providers are configured by the instance operator: [GitHub](/core/social-providers/github), [Google](/core/social-providers/google), [Discord](/core/social-providers/discord), or [custom OAuth/OIDC](/core/social-providers/custom-oauth).

For attachments, use [Silo](/core/installation/silo) or [another S3-compatible provider](/core/installation/storage-backends).

---

### Apps Docs Core Migrations Index (`apps/docs/core/migrations/index.mdx`)

---
title: Import from other tools
description: Bring projects from Linear, Jira, Trello, Plane, PLANKA, or another Kaneo instance into a Kaneo workspace.
---

Kaneo can copy projects from another tool into your workspace. You connect a source with a key, choose the projects you want, and Kaneo recreates them as new Kaneo projects. Kaneo only reads from the source; nothing there is changed. Imports work on [Kaneo Cloud](https://cloud.kaneo.app) and on self-hosted instances.

## Sources

| Source | Each of these becomes a Kaneo project | Guide |
| --- | --- | --- |
| Linear | Team | [Import from Linear](/core/migrations/from-linear) |
| Jira (Cloud or Data Center) | Project | [Import from Jira](/core/migrations/from-jira) |
| Trello | Board | [Import from Trello](/core/migrations/from-trello) |
| Plane (cloud or self-hosted) | Project | [Import from Plane](/core/migrations/from-plane) |
| PLANKA | Board | [Import from PLANKA](/core/migrations/from-planka) |
| Another Kaneo instance | Project | [Import from another Kaneo instance](/core/migrations/from-kaneo) |

## Start an import

Only workspace admins can import. Importing needs permission to manage workspace settings and to create projects, which owners and admins have by default.

<Steps>
  <Step title="Open the importer">
    Open **Settings → Workspace → Import**. You can also click **Import from another tool** on the projects page while the workspace has no projects, or **Import your projects** in the dialog for creating a new project.
  </Step>
  <Step title="Pick a source and connect">
    Choose where you are importing from and paste the key or token it needs. Each source guide explains where to find it.
  </Step>
  <Step title="Choose projects">
    Select the projects to import, up to 50 at a time. Comments are included by default; turn them off if you don't need them.
  </Step>
  <Step title="Watch the progress">
    The import runs on the server, so you can leave the page and come back. A workspace runs one import at a time. **Recent imports** lists past runs with a report for each project (counts and warnings) and a link to the new project.
  </Step>
</Steps>

## How imports work

These rules apply to every source.

### Each import creates new projects

Every selected source project becomes a new Kaneo project. Kaneo never updates or merges into an existing project, so running the same import twice gives you two copies.

If a project fails to import, nothing of it is left behind. If an import finishes but the result is not what you want, delete the project and import it again.

### Project keys and task numbers

The new project keeps the source's project key, for example `WEB` from Plane or `ENG` from Linear, when no other project in the workspace uses it. If the key is taken, Kaneo adds a suffix, for example `WEB-2`.

Tasks keep their numbers from the source, so `WEB-12` stays `WEB-12`. A task without a number in the source, or with a number another task in the same import already has, gets the next number after the highest one. Tasks you create later continue from there.

Trello boards have no key, so Kaneo makes one from the board name. PLANKA boards have no key or task numbers either, so Kaneo makes a key from the new project's name and numbers the tasks from 1 in board order.

### Invite your team first

Assignees are matched to workspace members by email. Anyone who isn't a member when you import gets their tasks imported unassigned. [Invite your team](/core/functional/collaborate-with-team) before you start.

Some sources hide email addresses. Trello never shares other people's emails, Jira often hides them depending on each person's profile settings, and PLANKA hides them from accounts that are not admins. In those cases Kaneo matches by full name, ignoring case, and only when exactly one workspace member has that name.

Kaneo has one assignee per task. When an item has several assignees, only the first is kept, and the report says how many items had more than one.

### Comments and notifications

Imported comments keep the original author's name and date, and are marked **Imported from** followed by the source name, for example **Imported from Linear**.

Imported tasks do not send notifications.

### Your key is not stored

The key or token you paste is used for that one import and is not stored. Kaneo only reads from the source. To import again later, paste the key again. You can revoke it in the source once the import is done.

## Self-hosted instances

The import runs on the Kaneo API server, not in your browser. The server must be able to reach the source.

By default, Kaneo blocks imports from addresses on a private network, for example a PLANKA container in the same Docker network or a Plane server on your LAN. To allow them, set `KANEO_ALLOW_PRIVATE_WEBHOOK_DESTINATIONS=true` on the API. This is the same switch that notification destinations (such as ntfy, Gotify, and webhooks) and the Gitea and GitLab integrations use, so only enable it on a trusted deployment. See [environment variables](/core/installation/environment-variables#imports).

The one-click **Authorize on Trello** button needs `TRELLO_API_KEY` on the API. See [Set up Trello on a self-hosted instance](/core/migrations/from-trello#set-up-trello-on-a-self-hosted-instance).

If your source is on a private network and your Kaneo instance can't reach it (for example, you use Kaneo Cloud), the in-app importer can't connect. For PLANKA, use the [command line importer](/core/migrations/from-planka#import-with-the-cli), which runs on your own machine.

---

### Apps Docs CONTRIBUTING (`apps/docs/CONTRIBUTING.md`)

# Contribute to the docs

Read the implementation behind a feature before documenting it. Test the instructions from the reader's starting point, including how they get any IDs, credentials, or files the example needs.

## Write for the person doing the work

- Use everyday English, short paragraphs, and sentence-case headings.
- Start with what the reader can accomplish and what they need first.
- Use the actual labels in the interface. Explain a term before relying on it.
- Avoid marketing filler, unnecessary warnings, and em dashes.
- Explain the result of an action, plus how to change or undo it when relevant.
- Keep setup tutorials separate from detailed configuration references.
- Use example.com domains and fictional people. Never include live credentials or private project data.

## Add useful images

Use real app screenshots with fictional data. Include both themes where available, descriptive alt text, and a caption that tells readers what to notice. Put the image next to the step it explains. Use diagrams for system relationships instead of inventing screenshots of an interface.

See [the screenshot guide](images/product/README.md) for reproduction. Verify the page at desktop and mobile widths, and check that the right screenshot appears in each theme.

## Check your change

From `apps/docs`, run `pnpm dlx mint validate` and `pnpm dlx mint broken-links`. Preview with `pnpm dlx mint dev` and follow the updated navigation and instructions in a browser.

Keep existing page URLs where possible. When a URL must change, add a redirect in `docs.json` and update internal links. Add new guide pages to the appropriate navigation group.

API route and schema changes require `pnpm openapi:check:fix` from the repository root. Do not hand-edit generated endpoint contracts to conceal a mismatch. Older links under `api-reference/endpoints/` are kept as thin pages pointing at the same schema; update their method and path when a route moves, and let OpenAPI supply their descriptions.

In your pull request, say which instructions you executed and which you only reviewed. A successful docs build does not prove an installation or restore works.

---

### Apps Docs Docs.Json (`apps/docs/docs.json`)

{
	"$schema": "https://mintlify.com/docs.json",
	"theme": "almond",
	"name": "Kaneo",
	"colors": {
		"primary": "#111111",
		"light": "#fafafa",
		"dark": "#1f1f1f"
	},
	"appearance": {
		"default": "dark"
	},
	"icons": {
		"library": "lucide"
	},
	"styling": {
		"codeblocks": {
			"theme": {
				"light": "min-light",
				"dark": "min-dark"
			}
		}
	},
	"fonts": {
		"family": "Geist Variable",
		"source": "https://kaneo.app/docs/fonts/geist.woff2",
		"format": "woff2"
	},
	"favicon": "/favicon.svg",
	"logo": {
		"light": "/logo/dark.svg",
		"dark": "/logo/light.svg"
	},
	"redirects": [
		{
			"source": "/core/guide/migrating-from-v1",
			"destination": "/core/installation/environment-variables"
		},
		{
			"source": "/core/integrations/cli",
			"destination": "/cli"
		},
		{
			"source": "/core/integrations/cli-commands",
			"destination": "/cli/reference"
		}
	],
	"navigation": {
		"tabs": [
			{
				"tab": "Using Kaneo",
				"groups": [
					{
						"group": "Start here",
						"pages": [
							"index",
							"core/index",
							"core/functional/index",
							"core/functional/create-workspace-and-project"
						]
					},
					{
						"group": "Work with tasks",
						"pages": [
							"core/functional/plan-and-execute-tasks",
							"core/functional/backlog-planning",
							"core/functional/calendar-and-gantt",
							"core/functional/configure-workflows",
							"core/functional/manage-workspace-labels"
						]
					},
					{
						"group": "Your team and account",
						"pages": [
							"core/functional/collaborate-with-team",
							"core/functional/account-notifications",
							"core/functional/personalize-your-workspace",
							"core/functional/manage-your-account"
						]
					},
					{
						"group": "Import from other tools",
						"pages": [
							"core/migrations/index",
							"core/migrations/from-linear",
							"core/migrations/from-jira",
							"core/migrations/from-trello",
							"core/migrations/from-plane",
							"core/migrations/from-planka",
							"core/migrations/from-kaneo"
						]
					}
				]
			},
			{
				"tab": "Self-hosting",
				"groups": [
					{
						"group": "Install",
						"pages": [
							"core/installation/index",
							"core/installation/docker-compose",
							"core/installation/drim",
							"core/deployments/coolify",
							"core/deployments/railway"
						]
					},
					{
						"group": "Configure",
						"pages": [
							"core/reverse-proxy/nginx",
							"core/installation/object-storage-and-image-uploads",
							"core/installation/silo",
							"core/installation/storage-backends",
							"core/installation/environment-variables"
						]
					},
					{
						"group": "Sign-in providers",
						"pages": [
							"core/social-providers/github",
							"core/social-providers/google",
							"core/social-providers/discord",
							"core/social-providers/custom-oauth"
						]
					},
					{
						"group": "Keep it running",
						"pages": [
							"core/operations/administration",
							"core/operations/backups",
							"core/operations/upgrades",
							"core/operations/troubleshooting"
						]
					},
					{
						"group": "Migrate a deployment",
						"pages": [
							"core/installation/migration",
							"core/installation/drim-migration"
						]
					}
				]
			},
			{
				"tab": "Integrations",
				"groups": [
					{
						"group": "Overview",
						"pages": ["core/integrations/index"]
					},
					{
						"group": "Repositories",
						"pages": [
							"core/integrations/github/setup",
							"core/integrations/github/configuration",
							"core/integrations/github/troubleshooting",
							"core/integrations/gitea/setup",
							"core/integrations/gitlab/setup"
						]
					},
					{
						"group": "Team updates",
						"pages": [
							"core/integrations/slack",
							"core/integrations/discord",
							"core/integrations/mattermost",
							"core/integrations/telegram",
							"core/integrations/outgoing-webhooks"
						]
					},
					{
						"group": "Personal notifications",
						"pages": [
							"core/integrations/ntfy",
							"core/integrations/gotify",
							"core/integrations/personal-webhooks"
						]
					},
					{
						"group": "AI assistants",
						"pages": ["core/integrations/mcp"]
					}
				]
			},
			{
				"tab": "Developers",
				"groups": [
					{
						"group": "Start building",
						"pages": [
							"api-reference/introduction",
							"api-reference/authentication",
							"api-reference/errors",
							"core/integrations/mcp-stateless"
						]
					},
					{
						"group": "Command line",
						"pages": [
							"cli/index",
							"cli/install",
							"cli/sign-in",
							"cli/configuration",
							"cli/scripting",
							"cli/reference"
						]
					},
					{
						"group": "Endpoint reference",
						"openapi": "openapi.json"
					}
				]
			}
		],
		"global": {
			"anchors": [
				{
					"anchor": "Website",
					"href": "https://kaneo.app",
					"icon": "globe"
				},
				{
					"anchor": "GitHub",
					"href": "https://github.com/usekaneo/kaneo",
					"icon": "github"
				}
			]
		}
	},
	"navbar": {
		"links": [
			{
				"label": "GitHub",
				"href": "https://github.com/usekaneo/kaneo"
			}
		],
		"primary": {
			"type": "button",
			"label": "Open Kaneo",
			"href": "https://cloud.kaneo.app"
		}
	},
	"contextual": {
		"options": ["copy", "view", "chatgpt", "claude", "cursor"]
	},
	"footer": {
		"socials": {
			"website": "https://kaneo.app",
			"github": "https://github.com/usekaneo/kaneo",
			"x": "https://x.com/usekaneo"
		}
	},
	"seo": {
		"indexing": "all",
		"metatags": {
			"og:site_name": "Kaneo Docs",
			"og:type": "website",
			"og:image": "https://kaneo.app/images/hero.png",
			"twitter:card": "summary_large_image",
			"twitter:site": "@usekaneo",
			"twitter:image": "https://kaneo.app/images/hero.png"
		}
	},
	"integrations": {
		"plausible": {
			"domain": "kaneo.app",
			"server": "plausible.kaneo.app"
		}
	}
}

---

### Apps Docs Openapi.Json (`apps/docs/openapi.json`)

{
  "openapi": "3.1.0",
  "info": {
    "title": "Kaneo API",
    "version": "1.0.0",
    "description": "Kaneo Project Management API - Manage projects, tasks, labels, and more"
  },
  "servers": [
    {
      "url": "https://cloud.kaneo.app/api",
      "description": "Kaneo API Server"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "responses": {
      "ApiKeyRateLimited": {
        "description": "The API key exceeded its rate limit or usage quota. Wait the number of seconds in Retry-After before retrying. The X-RateLimit headers are sent only when the rate limit was hit.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying. Omitted when an exhausted usage quota never refills.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Limit": {
            "description": "Requests the API key may make per window.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "description": "Requests left in the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "description": "Unix time in seconds when the current window resets.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key or session token (Bearer)"
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "description": "Human-readable description of the error.",
            "example": "Task not found"
          },
          "code": {
            "type": "string",
            "description": "Stable UPPER_SNAKE_CASE code. Derived from the status unless a more specific code applies, such as VALIDATION_ERROR or MISSING_PERMISSION.",
            "example": "NOT_FOUND"
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string",
                  "description": "Dotted path to the invalid input, prefixed with the request part (body, query, params, header).",
                  "example": "body.title"
                },
                "message": {
                  "type": "string",
                  "example": "Required"
                }
              },
              "required": [
                "path",
                "message"
              ]
            },
            "description": "Every validation issue, for VALIDATION_ERROR."
          },
          "missingPermissions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Permissions the caller lacks for this route, as resource:action, for MISSING_PERMISSION and API_KEY_SCOPE.",
            "example": [
              "task:update"
            ]
          }
        },
        "required": [
          "message",
          "code"
        ]
      },
      "InstanceStatus": {
        "type": "object",
        "properties": {
          "hasUsers": {
            "type": "boolean"
          },
          "hasAdmin": {
            "type": "boolean"
          }
        },
        "required": [
          "hasUsers",
          "hasAdmin"
        ]
      },
      "PublicBoardPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Board"
          },
          {
            "type": "object",
            "properties": {
              "pagination": {
                "$ref": "#/components/schemas/BoardPagination"
              }
            },
            "required": [
              "pagination"
            ]
          }
        ]
      },
      "BoardColumn": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The column slug, same as `slug`."
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "icon": {
            "type": [
              "string",
              "null"
            ]
          },
          "isFinal": {
            "type": "boolean"
          },
          "position": {
            "type": "number"
          },
          "tasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BoardTask"
            }
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "icon",
          "isFinal",
          "tasks"
        ]
      },
      "BoardTask": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "number": {
            "type": [
              "number",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "descriptionDeferred": {
            "type": "boolean",
            "description": "True when the list omits a large description; load the task detail or description pages to read it. Do not replace stored text with this null summary."
          },
          "status": {
            "type": "string"
          },
          "priority": {
            "type": "string",
            "description": "One of: no-priority, low, medium, high, urgent."
          },
          "startDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "dueDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "position": {
            "type": [
              "number",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "userId": {
            "type": [
              "string",
              "null"
            ]
          },
          "assigneeName": {
            "type": [
              "string",
              "null"
            ]
          },
          "assigneeId": {
            "type": [
              "string",
              "null"
            ]
          },
          "assigneeImage": {
            "type": [
              "string",
              "null"
            ]
          },
          "projectId": {
            "type": "string"
          },
          "subtaskCounts": {
            "type": "object",
            "properties": {
              "completed": {
                "type": "integer",
                "minimum": 0
              },
              "total": {
                "type": "integer",
                "minimum": 0
              }
            },
            "required": [
              "completed",
              "total"
            ],
            "description": "Direct subtasks in the workspace; completed means a final column in the child project. Public boards count only children in public projects."
          },
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaskLabel"
            }
          },
          "externalLinks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaskExternalLink"
            }
          }
        },
        "required": [
          "id",
          "title",
          "number",
          "description",
          "status",
          "priority",
          "startDate",
          "dueDate",
          "position",
          "createdAt",
          "userId",
          "assigneeName",
          "assigneeId",
          "assigneeImage",
          "projectId",
          "subtaskCounts",
          "labels",
          "externalLinks"
        ]
      },
      "TaskLabel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "color"
        ]
      },
      "TaskExternalLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "integrationId": {
            "type": [
              "string",
              "null"
            ]
          },
          "resourceType": {
            "type": "string"
          },
          "externalId": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {},
            "description": "Provider-specific payload, already parsed from the stored JSON string."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "taskId",
          "integrationId",
          "resourceType",
          "externalId",
          "url",
          "title",
          "metadata",
          "createdAt",
          "updatedAt"
        ]
      },
      "BoardPagination": {
        "type": "object",
        "properties": {
          "total": {
            "type": "number"
          },
          "page": {
            "type": "number"
          },
          "pageSize": {
            "type": "number"
          },
          "totalPages": {
            "type": "number"
          },
          "relatedPage": {
            "type": "number"
          },
          "relatedPageSize": {
            "type": "number"
          },
          "relatedTotalPages": {
            "type": "number"
          },
          "relatedRevision": {
            "type": "string",
            "description": "Public board labels and external links revision for this task page. Restart pagination if it changes during related-page continuations."
          },
          "revision": {
            "type": "string",
            "description": "Public board content, membership and ordering revision, including related records and visible subtask progress. Restart pagination if it changes between task or related pages."
          }
        },
        "required": [
          "total",
          "page",
          "pageSize",
          "totalPages",
          "relatedPage",
          "relatedPageSize",
          "relatedTotalPages"
        ],
        "description": "Always paginated: 50 tasks by default, at most 100 per page. Continue through totalPages to retrieve all tasks. For each task page, follow relatedPage through relatedTotalPages to retrieve all labels, external links and columns (100 related rows per kind per request, plus up to 100 columns needed to represent the tasks)."
      },
      "Board": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "icon": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "descriptionDeferred": {
            "type": "boolean",
            "description": "True when a project description above 64 KiB is omitted; use project detail or public project description pages for full text."
          },
          "isPublic": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "workspaceId": {
            "type": "string"
          },
          "backgroundVersion": {
            "type": [
              "string",
              "null"
            ]
          },
          "columns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BoardColumn"
            }
          },
          "archivedTasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BoardTask"
            }
          },
          "plannedTasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BoardTask"
            }
          }
        },
        "required": [
          "id",
          "name",
          "slug",
          "icon",
          "description",
          "isPublic",
          "workspaceId",
          "backgroundVersion",
          "columns",
          "archivedTasks",
          "plannedTasks"
        ]
      },
      "TaskDescriptionPage": {
        "type": "object",
        "properties": {
          "content": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "nextOffset": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "content",
          "version",
          "nextOffset"
        ]
      },
      "Config": {
        "type": "object",
        "properties": {
          "isCloud": {
            "type": "boolean"
          },
          "disableRegistration": {
            "type": "boolean"
          },
          "disablePasswordRegistration": {
            "type": "boolean"
          },
          "disableEmailOtpSignIn": {
            "type": "boolean"
          },
          "disableWorkspaceCreation": {
            "type": "boolean"
          },
          "isDemoMode": {
            "type": "boolean"
          },
          "hasSmtp": {
            "type": "boolean"
          },
          "hasImageUploads": {
            "type": "boolean",
            "description": "Whether S3-compatible storage is configured, so task images and board backgrounds can be uploaded."
          },
          "hasGithubSignIn": {
            "type": "boolean"
          },
          "hasGoogleSignIn": {
            "type": "boolean"
          },
          "hasDiscordSignIn": {
            "type": "boolean"
          },
          "hasCustomOAuth": {
            "type": "boolean"
          },
          "hasGuestAccess": {
            "type": "boolean"
          },
          "disableLoginForm": {
            "type": "boolean"
          },
          "customOAuthName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Label for the custom OAuth provider on the sign-in page. Null means the generic SSO label."
          },
          "customOAuthAutoLogin": {
            "type": "boolean"
          },
          "customOAuthLogoutUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "billingEnabled": {
            "type": "boolean"
          }
        },
        "required": [
          "isCloud",
          "disableRegistration",
          "disablePasswordRegistration",
          "disableEmailOtpSignIn",
          "disableWorkspaceCreation",
          "isDemoMode",
          "hasSmtp",
          "hasImageUploads",
          "hasGithubSignIn",
          "hasGoogleSignIn",
          "hasDiscordSignIn",
          "hasCustomOAuth",
          "hasGuestAccess",
          "disableLoginForm",
          "customOAuthName",
          "customOAuthAutoLogin",
          "customOAuthLogoutUrl",
          "billingEnabled"
        ]
      },
      "McpClientRegistration": {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string"
          },
          "client_id_issued_at": {
            "type": "number"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "client_name": {
            "type": "string"
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "none"
            ]
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code"
              ]
            }
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          }
        },
        "required": [
          "client_id",
          "client_id_issued_at",
          "redirect_uris",
          "token_endpoint_auth_method",
          "grant_types",
          "response_types"
        ]
      },
      "OAuthError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "McpAuthorizationRequest": {
        "type": "object",
        "properties": {
          "client_name": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string"
          }
        },
        "required": [
          "client_name",
          "redirect_uri"
        ]
      },
      "McpAuthorizationDecision": {
        "type": "object",
        "properties": {
          "redirect": {
            "type": "string",
            "description": "Where to send the browser next: back to the client with a code on approval, or with an error on denial."
          }
        },
        "required": [
          "redirect"
        ]
      },
      "OAuthIdToken": {
        "type": "object",
        "properties": {
          "idToken": {
            "type": [
              "string",
              "null"
            ],
            "description": "The stored id_token for the user's `custom` OAuth account, or null when they have no such account."
          }
        },
        "required": [
          "idToken"
        ]
      },
      "MovedProject": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Project"
          },
          {
            "type": "object",
            "properties": {
              "unassignedTaskCount": {
                "type": "number"
              }
            },
            "required": [
              "unassignedTaskCount"
            ]
          }
        ]
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "workspaceId": {
            "type": "string"
          },
          "backgroundVersion": {
            "type": [
              "string",
              "null"
            ]
          },
          "slug": {
            "type": "string",
            "description": "Short prefix used in task identifiers, e.g. KAN-12."
          },
          "icon": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "isPublic": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "When true the project's board is readable without signing in, via /api/public-project/{id}."
          },
          "archivedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Non-null once archived; archived projects are hidden by default."
          },
          "position": {
            "type": "number",
            "description": "Sidebar order, ascending."
          },
          "lastTaskNumber": {
            "type": "number",
            "description": "Highest task number issued in this project; the next task gets this plus one."
          }
        },
        "required": [
          "id",
          "workspaceId",
          "backgroundVersion",
          "slug",
          "icon",
          "name",
          "description",
          "createdAt",
          "isPublic",
          "archivedAt",
          "position",
          "lastTaskNumber"
        ]
      },
      "ProjectListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Project"
          },
          {
            "type": "object",
            "properties": {
              "statistics": {
                "$ref": "#/components/schemas/ProjectStatistics"
              },
              "archivedTasks": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BoardTask"
                },
                "description": "Always empty."
              },
              "plannedTasks": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BoardTask"
                },
                "description": "Always empty."
              },
              "columns": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BoardColumn"
                },
                "description": "Always empty."
              }
            },
            "required": [
              "statistics",
              "archivedTasks",
              "plannedTasks",
              "columns"
            ]
          }
        ]
      },
      "ProjectStatistics": {
        "type": "object",
        "properties": {
          "completionPercentage": {
            "type": "number"
          },
          "totalTasks": {
            "type": "number"
          },
          "dueDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The soonest due date among the project's open tasks."
          }
        },
        "required": [
          "completionPercentage",
          "totalTasks",
          "dueDate"
        ]
      },
      "ProjectBackgroundUpload": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "uploadUrl": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "required": [
          "key",
          "uploadUrl",
          "version",
          "headers"
        ]
      },
      "ProjectBackgroundFinalize": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          }
        },
        "required": [
          "url"
        ]
      },
      "CalendarFeed": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "token": {
            "type": "string"
          },
          "labelIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "timeZone": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "token",
          "labelIds",
          "timeZone",
          "createdAt"
        ]
      },
      "TaskDescriptionMatches": {
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "ids",
          "nextCursor"
        ]
      },
      "AssignedTasks": {
        "type": "object",
        "properties": {
          "tasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AssignedTask"
            }
          },
          "total": {
            "type": "number",
            "description": "All open tasks assigned to the caller, including any beyond the first 100 returned."
          }
        },
        "required": [
          "tasks",
          "total"
        ]
      },
      "AssignedTask": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "number": {
            "type": [
              "number",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "The slug of the column the task sits in."
          },
          "statusName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The column's display name. Null for tasks outside a column, such as planned ones."
          },
          "statusIcon": {
            "type": [
              "string",
              "null"
            ]
          },
          "priority": {
            "type": "string",
            "description": "One of: no-priority, low, medium, high, urgent."
          },
          "dueDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "projectName": {
            "type": "string"
          },
          "projectSlug": {
            "type": "string"
          },
          "projectIcon": {
            "type": [
              "string",
              "null"
            ]
          },
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaskLabel"
            }
          }
        },
        "required": [
          "id",
          "projectId",
          "number",
          "title",
          "status",
          "statusName",
          "statusIcon",
          "priority",
          "dueDate",
          "projectName",
          "projectSlug",
          "projectIcon",
          "labels"
        ]
      },
      "BoardResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Board"
          },
          "pagination": {
            "$ref": "#/components/schemas/BoardPagination"
          }
        },
        "required": [
          "data",
          "pagination"
        ]
      },
      "BulkTaskResult": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "updatedCount": {
            "type": "number"
          }
        },
        "required": [
          "success",
          "updatedCount"
        ]
      },
      "TaskImageUpload": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Object key to send back to the finalize route."
          },
          "uploadUrl": {
            "type": "string",
            "description": "Presigned URL to PUT the image bytes to."
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Headers that must accompany the upload request, including Content-Type."
          }
        },
        "required": [
          "key",
          "uploadUrl",
          "headers"
        ]
      },
      "TaskImageAsset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Where the stored image can be read back from."
          }
        },
        "required": [
          "id",
          "url"
        ]
      },
      "Task": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "position": {
            "type": [
              "number",
              "null"
            ],
            "description": "Order within its column, ascending."
          },
          "number": {
            "type": [
              "number",
              "null"
            ],
            "description": "Per-project counter shown as {projectSlug}-{number}."
          },
          "userId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The assignee, if any."
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "descriptionDeferred": {
            "type": "boolean",
            "description": "True when the list omits a large description; load the task detail or description pages to read it. Do not replace stored text with this null summary."
          },
          "status": {
            "type": "string",
            "description": "The slug of the column the task sits in."
          },
          "priority": {
            "type": "string",
            "description": "One of: no-priority, low, medium, high, urgent."
          },
          "startDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "dueDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "customFields": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "fieldId": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              },
              "required": [
                "fieldId",
                "value"
              ]
            }
          }
        },
        "required": [
          "id",
          "projectId",
          "position",
          "number",
          "userId",
          "title",
          "description",
          "status",
          "priority",
          "startDate",
          "dueDate",
          "createdAt"
        ]
      },
      "TaskByTicketId": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TaskWithAssignee"
          },
          {
            "type": "object",
            "properties": {
              "workspaceId": {
                "type": "string",
                "description": "The workspace that owns the task's project."
              }
            },
            "required": [
              "workspaceId"
            ]
          }
        ]
      },
      "TaskWithAssignee": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Task"
          },
          {
            "type": "object",
            "properties": {
              "workspaceId": {
                "type": "string",
                "description": "The workspace currently owning the task's project. Included in the detail view; omitted from the compact board view."
              },
              "columnId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The referenced workflow column; null for virtual statuses and legacy tasks without a column reference."
              },
              "subtaskCounts": {
                "type": "object",
                "properties": {
                  "completed": {
                    "type": "number"
                  },
                  "total": {
                    "type": "number"
                  }
                },
                "required": [
                  "completed",
                  "total"
                ]
              },
              "parentSubtaskCounts": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "taskId": {
                      "type": "string"
                    },
                    "completed": {
                      "type": "number"
                    },
                    "total": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "taskId",
                    "completed",
                    "total"
                  ]
                }
              },
              "assigneeName": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "assigneeId": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "columnId",
              "assigneeName",
              "assigneeId"
            ]
          }
        ]
      },
      "MoveTaskResult": {
        "type": "object",
        "properties": {
          "task": {
            "$ref": "#/components/schemas/Task"
          },
          "sourceProjectId": {
            "type": "string"
          },
          "destinationProjectId": {
            "type": "string"
          }
        },
        "required": [
          "task",
          "sourceProjectId",
          "destinationProjectId"
        ]
      },
      "TaskExport": {
        "type": "object",
        "properties": {
          "project": {
            "$ref": "#/components/schemas/TaskExportProject"
          },
          "tasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportedTask"
            }
          }
        },
        "required": [
          "project",
          "tasks"
        ]
      },
      "TaskExportProject": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "exportedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "name",
          "slug",
          "description",
          "exportedAt"
        ]
      },
      "ExportedTask": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "priority": {
            "type": "string"
          },
          "dueDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "startDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "userId": {
            "type": [
              "string",
              "null"
            ]
          },
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportedTaskLabel"
            },
            "description": "Label names and colors, without their ids."
          }
        },
        "required": [
          "title",
          "description",
          "status",
          "priority",
          "dueDate",
          "startDate",
          "userId",
          "labels"
        ]
      },
      "ExportedTaskLabel": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "color": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "color"
        ]
      },
      "TaskImportResult": {
        "type": "object",
        "properties": {
          "results": {
            "$ref": "#/components/schemas/TaskImportSummary"
          }
        },
        "required": [
          "results"
        ]
      },
      "TaskImportSummary": {
        "type": "object",
        "properties": {
          "total": {
            "type": "number"
          },
          "successful": {
            "type": "number"
          },
          "failed": {
            "type": "number"
          },
          "tasks": {
            "type": "array",
            "items": {},
            "description": "Per-task outcome, each carrying a success flag."
          }
        },
        "required": [
          "total",
          "successful",
          "failed",
          "tasks"
        ]
      },
      "Column": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "Stable identifier derived from the name; tasks store this as their status."
          },
          "position": {
            "type": "number",
            "description": "Board order, ascending. Columns are always returned sorted by it."
          },
          "icon": {
            "type": [
              "string",
              "null"
            ]
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          },
          "isFinal": {
            "type": "boolean",
            "description": "Marks the column as a done state, which stops overdue reminders for tasks in it."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "name",
          "slug",
          "position",
          "icon",
          "color",
          "isFinal",
          "createdAt",
          "updatedAt"
        ]
      },
      "WorkspaceActivity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "One of: comment, task, create, status_changed, priority_changed, assignee_changed, unassigned, due_date_changed, title_changed, description_changed."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "eventData": {
            "description": "Type-specific payload. For status_changed, oldStatus/newStatus are slugs and oldStatusName/newStatusName carry current configured column names when available."
          },
          "excerpt": {
            "type": [
              "string",
              "null"
            ],
            "description": "Plain-text preview of the stored content, at most 240 characters: the comment body, or the description older system events carry instead of eventData."
          },
          "userId": {
            "type": [
              "string",
              "null"
            ]
          },
          "userName": {
            "type": [
              "string",
              "null"
            ]
          },
          "userImage": {
            "type": [
              "string",
              "null"
            ]
          },
          "externalUserName": {
            "type": [
              "string",
              "null"
            ]
          },
          "externalUserAvatar": {
            "type": [
              "string",
              "null"
            ]
          },
          "taskId": {
            "type": "string"
          },
          "taskTitle": {
            "type": "string"
          },
          "taskNumber": {
            "type": [
              "number",
              "null"
            ]
          },
          "projectId": {
            "type": "string"
          },
          "projectSlug": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "excerpt",
          "userId",
          "userName",
          "userImage",
          "externalUserName",
          "externalUserAvatar",
          "taskId",
          "taskTitle",
          "taskNumber",
          "projectId",
          "projectSlug"
        ]
      },
      "Activity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "One of: comment, task, create, status_changed, priority_changed, assignee_changed, unassigned, due_date_changed, title_changed, description_changed."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "userId": {
            "type": [
              "string",
              "null"
            ]
          },
          "content": {
            "type": [
              "string",
              "null"
            ]
          },
          "eventData": {
            "description": "Type-specific payload, e.g. { oldStatus, newStatus } for status_changed. Null for plain comments."
          },
          "externalUserName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when the activity was imported from another tool."
          },
          "externalUserAvatar": {
            "type": [
              "string",
              "null"
            ]
          },
          "externalSource": {
            "type": [
              "string",
              "null"
            ],
            "description": "The tool it was imported from: planka, trello, jira, plane, linear, kaneo, or a connected forge such as github."
          },
          "externalUrl": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "taskId",
          "type",
          "createdAt",
          "updatedAt",
          "userId",
          "content",
          "externalUserName",
          "externalUserAvatar",
          "externalSource",
          "externalUrl"
        ]
      },
      "Comment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "userId": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "user": {
            "$ref": "#/components/schemas/CommentAuthor"
          }
        },
        "required": [
          "id",
          "taskId",
          "userId",
          "content",
          "createdAt",
          "updatedAt",
          "user"
        ]
      },
      "CommentAuthor": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "image"
        ]
      },
      "TimeEntryWithUser": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TimeEntry"
          },
          {
            "type": "object",
            "properties": {
              "userName": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "userName"
            ]
          }
        ]
      },
      "TimeEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "userId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null once the user who logged the time has been removed."
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Null while the timer is still running."
          },
          "duration": {
            "type": [
              "number",
              "null"
            ],
            "description": "Elapsed seconds, filled in once the entry has an endTime."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "taskId",
          "userId",
          "description",
          "startTime",
          "endTime",
          "duration",
          "createdAt",
          "updatedAt"
        ]
      },
      "Label": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "deletionStartedAt": {
            "type": "string",
            "format": "date-time"
          },
          "taskId": {
            "type": [
              "string",
              "null"
            ]
          },
          "workspaceId": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "color",
          "createdAt",
          "updatedAt",
          "taskId",
          "workspaceId"
        ]
      },
      "PendingLabelDeletion": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Label"
          },
          {
            "type": "object",
            "properties": {
              "pendingDeletion": {
                "type": "boolean",
                "enum": [
                  true
                ]
              }
            },
            "required": [
              "pendingDeletion"
            ]
          }
        ]
      },
      "Notification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "userId": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "content": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-text body. Null for generated notifications, whose text the client renders from type and eventData."
          },
          "type": {
            "type": "string",
            "description": "One of: info, task_created, workspace_created, task_status_changed, task_assignee_changed, time_entry_created, due_date_reminder, task_overdue, task_mention, task_comment."
          },
          "eventData": {
            "description": "Type-specific payload, e.g. { taskTitle, oldStatus, newStatus, projectId, workspaceId }."
          },
          "isRead": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "resourceId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The id of the task or workspace the notification points at."
          },
          "resourceType": {
            "type": [
              "string",
              "null"
            ],
            "description": "`task` or `workspace`."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "userId",
          "title",
          "content",
          "type",
          "isRead",
          "resourceId",
          "resourceType",
          "createdAt",
          "updatedAt"
        ]
      },
      "NotificationBulkResult": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          }
        },
        "required": [
          "success"
        ]
      },
      "NotificationPreferences": {
        "type": "object",
        "properties": {
          "emailAddress": {
            "type": [
              "string",
              "null"
            ]
          },
          "emailEnabled": {
            "type": "boolean"
          },
          "ntfyEnabled": {
            "type": "boolean"
          },
          "ntfyConfigured": {
            "type": "boolean"
          },
          "ntfyServerUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "ntfyTopic": {
            "type": [
              "string",
              "null"
            ]
          },
          "ntfyTokenConfigured": {
            "type": "boolean"
          },
          "maskedNtfyToken": {
            "type": [
              "string",
              "null"
            ]
          },
          "gotifyEnabled": {
            "type": "boolean"
          },
          "gotifyConfigured": {
            "type": "boolean"
          },
          "gotifyServerUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "gotifyTokenConfigured": {
            "type": "boolean"
          },
          "maskedGotifyToken": {
            "type": [
              "string",
              "null"
            ]
          },
          "webhookEnabled": {
            "type": "boolean"
          },
          "webhookConfigured": {
            "type": "boolean"
          },
          "webhookUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "webhookSecretConfigured": {
            "type": "boolean"
          },
          "maskedWebhookSecret": {
            "type": [
              "string",
              "null"
            ]
          },
          "taskAssignmentEnabled": {
            "type": "boolean"
          },
          "taskCommentEnabled": {
            "type": "boolean"
          },
          "taskStatusChangeEnabled": {
            "type": "boolean"
          },
          "dueDateReminderEnabled": {
            "type": "boolean"
          },
          "dueDateReminderLeadTimeMinutes": {
            "type": "number",
            "description": "How long before a due date the reminder fires, in minutes."
          },
          "workspaces": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NotificationPreferenceWorkspaceRule"
            },
            "description": "Per-workspace overrides. A workspace with no rule follows the global settings."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "emailAddress",
          "emailEnabled",
          "ntfyEnabled",
          "ntfyConfigured",
          "ntfyServerUrl",
          "ntfyTopic",
          "ntfyTokenConfigured",
          "maskedNtfyToken",
          "gotifyEnabled",
          "gotifyConfigured",
          "gotifyServerUrl",
          "gotifyTokenConfigured",
          "maskedGotifyToken",
          "webhookEnabled",
          "webhookConfigured",
          "webhookUrl",
          "webhookSecretConfigured",
          "maskedWebhookSecret",
          "taskAssignmentEnabled",
          "taskCommentEnabled",
          "taskStatusChangeEnabled",
          "dueDateReminderEnabled",
          "dueDateReminderLeadTimeMinutes",
          "workspaces",
          "createdAt",
          "updatedAt"
        ]
      },
      "NotificationPreferenceWorkspaceRule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "workspaceId": {
            "type": "string"
          },
          "workspaceName": {
            "type": "string"
          },
          "isActive": {
            "type": "boolean",
            "description": "Turn all notifications for this workspace on or off."
          },
          "emailEnabled": {
            "type": "boolean"
          },
          "ntfyEnabled": {
            "type": "boolean"
          },
          "gotifyEnabled": {
            "type": "boolean"
          },
          "webhookEnabled": {
            "type": "boolean"
          },
          "projectMode": {
            "type": "string",
            "enum": [
              "all",
              "selected"
            ],
            "description": "`all` notifies for every project in the workspace; `selected` restricts it to selectedProjectIds."
          },
          "selectedProjectIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "workspaceId",
          "workspaceName",
          "isActive",
          "emailEnabled",
          "ntfyEnabled",
          "gotifyEnabled",
          "webhookEnabled",
          "projectMode",
          "selectedProjectIds",
          "createdAt",
          "updatedAt"
        ]
      },
      "SearchResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "totalCount": {
            "type": "number",
            "description": "Total matches found before the `limit` slice, so a caller can tell that more exist."
          },
          "searchQuery": {
            "type": "string",
            "description": "The query that was run."
          }
        },
        "required": [
          "results",
          "totalCount",
          "searchQuery"
        ]
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "task",
              "project",
              "workspace",
              "comment",
              "activity"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "content": {
            "type": "string",
            "description": "Matched body text, for comment and activity results. Activities get a rendered summary such as `changed status from Todo to In Progress`."
          },
          "projectId": {
            "type": "string"
          },
          "projectName": {
            "type": "string"
          },
          "projectSlug": {
            "type": "string"
          },
          "workspaceId": {
            "type": "string"
          },
          "workspaceName": {
            "type": "string"
          },
          "userId": {
            "type": "string"
          },
          "userName": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "relevanceScore": {
            "type": "number",
            "description": "Higher is a better match. Results are sorted by score, then by createdAt descending."
          },
          "taskNumber": {
            "type": "number"
          },
          "priority": {
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "type",
          "title",
          "createdAt",
          "relevanceScore"
        ]
      },
      "IntegrationSyncPreview": {
        "type": "object",
        "properties": {
          "isActive": {
            "type": "boolean"
          },
          "rules": {
            "type": "object",
            "properties": {
              "outgoing": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "mode": {
                        "type": "string",
                        "enum": [
                          "all"
                        ]
                      }
                    },
                    "required": [
                      "mode"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "mode": {
                        "type": "string",
                        "enum": [
                          "labels"
                        ]
                      },
                      "match": {
                        "type": "string",
                        "enum": [
                          "any",
                          "all"
                        ]
                      },
                      "labels": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 128
                        },
                        "minItems": 1,
                        "maxItems": 50
                      }
                    },
                    "required": [
                      "mode",
                      "match",
                      "labels"
                    ]
                  }
                ],
                "description": "Kaneo workspace label IDs controlling task sync. Missing labels pause sync."
              },
              "incoming": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "mode": {
                        "type": "string",
                        "enum": [
                          "all"
                        ]
                      }
                    },
                    "required": [
                      "mode"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "mode": {
                        "type": "string",
                        "enum": [
                          "labels"
                        ]
                      },
                      "match": {
                        "type": "string",
                        "enum": [
                          "any",
                          "all"
                        ]
                      },
                      "labels": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 128
                        },
                        "minItems": 1,
                        "maxItems": 50
                      }
                    },
                    "required": [
                      "mode",
                      "match",
                      "labels"
                    ]
                  }
                ],
                "description": "Exact repository label names controlling which issues can be imported."
              }
            },
            "required": [
              "outgoing",
              "incoming"
            ]
          },
          "labels": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "color": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name",
                "color"
              ]
            }
          },
          "missingLabels": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "total": {
            "type": "number"
          },
          "matching": {
            "type": "number"
          },
          "willCreate": {
            "type": "number"
          },
          "willPause": {
            "type": "number"
          },
          "needsReview": {
            "type": "number"
          },
          "paused": {
            "type": "number"
          },
          "matchingTasks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "number": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "title": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "number",
                "title"
              ]
            }
          },
          "pausedNextCursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "pausedTasks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "number": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "title": {
                  "type": "string"
                },
                "linkId": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "eligible": {
                  "type": "boolean"
                }
              },
              "required": [
                "id",
                "number",
                "title",
                "linkId",
                "url",
                "eligible"
              ]
            }
          },
          "previewToken": {
            "type": "string"
          }
        },
        "required": [
          "isActive",
          "rules",
          "labels",
          "missingLabels",
          "total",
          "matching",
          "willCreate",
          "willPause",
          "needsReview",
          "paused",
          "matchingTasks",
          "pausedNextCursor",
          "pausedTasks",
          "previewToken"
        ]
      },
      "IntegrationSyncResumePreview": {
        "type": "object",
        "properties": {
          "task": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "number": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "title": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "number",
              "title"
            ]
          },
          "local": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "state": {
                "type": "string",
                "enum": [
                  "open",
                  "closed"
                ]
              }
            },
            "required": [
              "title",
              "description",
              "state"
            ]
          },
          "remote": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "state": {
                "type": "string",
                "enum": [
                  "open",
                  "closed"
                ]
              }
            },
            "required": [
              "title",
              "description",
              "state"
            ]
          },
          "token": {
            "type": "string"
          }
        },
        "required": [
          "task",
          "local",
          "remote",
          "token"
        ]
      },
      "GitHubAppInfo": {
        "type": "object",
        "properties": {
          "accountConnected": {
            "type": "boolean"
          },
          "accountLinkingAvailable": {
            "type": "boolean"
          },
          "appName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The configured GitHub App slug, or null when this instance has no App set up."
          }
        },
        "required": [
          "accountConnected",
          "accountLinkingAvailable",
          "appName"
        ]
      },
      "GitHubRepositoryList": {
        "type": "object",
        "properties": {
          "repositories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GitHubRepository"
            },
            "description": "Repositories the caller administers, deduplicated within the response."
          },
          "installations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GitHubInstallation"
            }
          },
          "total": {
            "type": "number"
          },
          "nextPage": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "installationPage": {
                "type": "number"
              },
              "repositoryPage": {
                "type": "number"
              }
            },
            "required": [
              "installationPage",
              "repositoryPage"
            ],
            "description": "Where to continue listing, or null when every installation has been read."
          }
        },
        "required": [
          "repositories",
          "installations",
          "total",
          "nextPage"
        ]
      },
      "GitHubRepository": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number"
          },
          "name": {
            "type": "string"
          },
          "full_name": {
            "type": "string"
          },
          "private": {
            "type": "boolean"
          },
          "owner": {
            "$ref": "#/components/schemas/GitHubRepositoryOwner"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "html_url": {
            "type": "string"
          },
          "permissions": {
            "$ref": "#/components/schemas/GitHubRepositoryPermissions"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "installation_id": {
            "type": "number",
            "description": "Which App installation this repository came through."
          }
        },
        "required": [
          "id",
          "name",
          "full_name",
          "private",
          "owner",
          "description",
          "html_url",
          "updated_at",
          "installation_id"
        ]
      },
      "GitHubRepositoryOwner": {
        "type": "object",
        "properties": {
          "login": {
            "type": "string"
          },
          "avatar_url": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "login",
          "avatar_url",
          "type"
        ]
      },
      "GitHubRepositoryPermissions": {
        "type": "object",
        "properties": {
          "admin": {
            "type": "boolean"
          },
          "push": {
            "type": "boolean"
          },
          "pull": {
            "type": "boolean"
          }
        },
        "required": [
          "admin",
          "push",
          "pull"
        ]
      },
      "GitHubInstallation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number"
          },
          "account": {
            "$ref": "#/components/schemas/GitHubInstallationAccount"
          },
          "repositories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Full names of the repositories under this installation. Empty when listing them failed."
          }
        },
        "required": [
          "id",
          "account",
          "repositories"
        ]
      },
      "GitHubInstallationAccount": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "login": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "login",
          "type"
        ]
      },
      "GitHubVerificationResult": {
        "type": "object",
        "properties": {
          "isInstalled": {
            "type": "boolean"
          },
          "installationId": {
            "type": [
              "number",
              "null"
            ]
          },
          "repositoryExists": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "repositoryPrivate": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "permissions": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            },
            "description": "The permissions the installation currently grants."
          },
          "hasRequiredPermissions": {
            "type": "boolean"
          },
          "missingPermissions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Permissions that must be granted before the link will work."
          },
          "message": {
            "type": "string",
            "description": "A human-readable summary to show the user."
          },
          "settingsUrl": {
            "type": "string",
            "description": "Where to adjust the installation, when one exists."
          },
          "installationUrl": {
            "type": "string",
            "description": "Where to install the App, when it is not installed yet."
          }
        },
        "required": [
          "isInstalled",
          "installationId",
          "repositoryExists",
          "repositoryPrivate",
          "permissions",
          "hasRequiredPermissions",
          "missingPermissions",
          "message"
        ]
      },
      "GitHubIntegration": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "repositoryOwner": {
            "type": "string"
          },
          "repositoryName": {
            "type": "string"
          },
          "installationId": {
            "type": [
              "number",
              "null"
            ],
            "description": "The GitHub App installation that grants access to the repository."
          },
          "requiresVerification": {
            "type": "boolean"
          },
          "importProgress": {
            "$ref": "#/components/schemas/IssueImportResult"
          },
          "branchPattern": {
            "type": "string",
            "description": "Template used to name branches created for a task."
          },
          "commentTaskLinkOnGitHubIssue": {
            "type": "boolean",
            "description": "When on, Kaneo comments a link back to the task on the linked GitHub issue."
          },
          "isActive": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "repositoryOwner",
          "repositoryName",
          "installationId",
          "requiresVerification",
          "isActive",
          "createdAt",
          "updatedAt"
        ]
      },
      "IssueImportResult": {
        "type": "object",
        "properties": {
          "runId": {
            "type": "string"
          },
          "pending": {
            "type": "boolean"
          },
          "imported": {
            "type": "number"
          },
          "updated": {
            "type": "number"
          },
          "skipped": {
            "type": "number",
            "description": "Issues whose linked task or source disappeared or belongs to another project."
          }
        },
        "required": [
          "runId",
          "pending",
          "imported",
          "updated",
          "skipped"
        ]
      },
      "CreatedGitHubIntegration": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "repositoryOwner": {
            "type": "string"
          },
          "repositoryName": {
            "type": "string"
          },
          "installationId": {
            "type": [
              "number",
              "null"
            ],
            "description": "The GitHub App installation that grants access to the repository."
          },
          "requiresVerification": {
            "type": "boolean"
          },
          "importProgress": {
            "$ref": "#/components/schemas/IssueImportResult"
          },
          "branchPattern": {
            "type": "string",
            "description": "Template used to name branches created for a task."
          },
          "commentTaskLinkOnGitHubIssue": {
            "type": "boolean",
            "description": "When on, Kaneo comments a link back to the task on the linked GitHub issue."
          },
          "isActive": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "repositoryOwner",
          "repositoryName",
          "installationId",
          "requiresVerification"
        ]
      },
      "GitHubDeleteResult": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "message"
        ]
      },
      "GiteaRepositoryList": {
        "type": "object",
        "properties": {
          "repositories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GiteaRepository"
            }
          }
        },
        "required": [
          "repositories"
        ]
      },
      "GiteaRepository": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number"
          },
          "name": {
            "type": "string"
          },
          "full_name": {
            "type": "string"
          },
          "owner": {
            "$ref": "#/components/schemas/GiteaRepositoryOwner"
          },
          "private": {
            "type": "boolean"
          },
          "html_url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "full_name",
          "owner",
          "private",
          "html_url"
        ]
      },
      "GiteaRepositoryOwner": {
        "type": "object",
        "properties": {
          "login": {
            "type": "string"
          }
        },
        "required": [
          "login"
        ]
      },
      "GiteaVerificationResult": {
        "type": "object",
        "properties": {
          "isInstalled": {
            "type": "boolean"
          },
          "hasRequiredPermissions": {
            "type": "boolean"
          },
          "repositoryExists": {
            "type": "boolean"
          },
          "repositoryPrivate": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "missingPermissions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "message": {
            "type": "string",
            "description": "A human-readable summary to show the user."
          },
          "failureReason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "not_a_gitea_instance",
              "redirected",
              "repository_not_found",
              null
            ],
            "description": "Why verification failed, when it did. `redirected` usually means the base URL is behind a proxy that rewrites it."
          }
        },
        "required": [
          "isInstalled",
          "hasRequiredPermissions",
          "repositoryExists",
          "repositoryPrivate",
          "missingPermissions",
          "message",
          "failureReason"
        ]
      },
      "GiteaIntegration": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "baseUrl": {
            "type": "string",
            "description": "Root URL of the Gitea instance."
          },
          "repositoryOwner": {
            "type": "string"
          },
          "repositoryName": {
            "type": "string"
          },
          "maskedAccessToken": {
            "type": "string"
          },
          "webhookUrl": {
            "type": "string",
            "description": "Where Gitea should POST events for this project."
          },
          "webhookSecret": {
            "type": "string",
            "description": "Only returned to callers with workspace:manage_settings, so the value can be pasted into Gitea."
          },
          "branchPattern": {
            "type": "string"
          },
          "commentTaskLinkOnGiteaIssue": {
            "type": "boolean",
            "description": "When on, Kaneo comments a link back to the task on the linked Gitea issue."
          },
          "isActive": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "baseUrl",
          "repositoryOwner",
          "repositoryName",
          "maskedAccessToken",
          "isActive",
          "createdAt",
          "updatedAt"
        ]
      },
      "GiteaDeleteResult": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "message"
        ]
      },
      "GiteaImportResult": {
        "type": "object",
        "properties": {
          "imported": {
            "type": "number"
          },
          "updated": {
            "type": "number",
            "description": "Existing tasks that were refreshed from their issue."
          },
          "skipped": {
            "type": "number"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "imported",
          "updated",
          "skipped"
        ]
      },
      "GitlabProjectList": {
        "type": "object",
        "properties": {
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GitlabProject"
            }
          }
        },
        "required": [
          "projects"
        ]
      },
      "GitlabProject": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number"
          },
          "name": {
            "type": "string"
          },
          "path_with_namespace": {
            "type": "string"
          },
          "name_with_namespace": {
            "type": "string"
          },
          "visibility": {
            "type": "string"
          },
          "web_url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "path_with_namespace",
          "name_with_namespace",
          "visibility",
          "web_url"
        ]
      },
      "GitlabVerificationResult": {
        "type": "object",
        "properties": {
          "isInstalled": {
            "type": "boolean",
            "description": "The base URL answered as a GitLab instance for this token."
          },
          "hasRequiredPermissions": {
            "type": "boolean"
          },
          "projectExists": {
            "type": "boolean"
          },
          "projectVisibility": {
            "type": [
              "string",
              "null"
            ]
          },
          "missingPermissions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "message": {
            "type": "string",
            "description": "A human-readable summary to show the user."
          },
          "failureReason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "not_a_gitlab_instance",
              "redirected",
              "project_not_found",
              null
            ],
            "description": "Why verification failed, when it did. `redirected` usually means the base URL is behind a proxy that rewrites it."
          }
        },
        "required": [
          "isInstalled",
          "hasRequiredPermissions",
          "projectExists",
          "projectVisibility",
          "missingPermissions",
          "message",
          "failureReason"
        ]
      },
      "GitlabIntegration": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "baseUrl": {
            "type": "string",
            "description": "Root URL of the GitLab instance."
          },
          "projectPath": {
            "type": "string",
            "description": "Full path of the linked project, nested groups included."
          },
          "tokenType": {
            "type": "string",
            "enum": [
              "private",
              "bearer"
            ]
          },
          "maskedAccessToken": {
            "type": "string",
            "description": "First and last characters of the stored token, only returned to callers with workspace:manage_settings."
          },
          "webhookUrl": {
            "type": "string",
            "description": "Where GitLab should POST events for this project."
          },
          "webhookSecret": {
            "type": "string",
            "description": "Only returned to callers with workspace:manage_settings, so the value can be pasted into GitLab."
          },
          "branchPattern": {
            "type": "string"
          },
          "commentTaskLinkOnGitlabIssue": {
            "type": "boolean",
            "description": "When on, Kaneo comments a link back to the task on each issue opened in GitLab. Issues Kaneo creates from tasks are not commented on."
          },
          "isActive": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "baseUrl",
          "projectPath",
          "tokenType",
          "maskedAccessToken",
          "isActive",
          "createdAt",
          "updatedAt"
        ]
      },
      "GitlabDeleteResult": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "message"
        ]
      },
      "GitlabImportResult": {
        "type": "object",
        "properties": {
          "imported": {
            "type": "number"
          },
          "updated": {
            "type": "number",
            "description": "Existing tasks that were refreshed from their issue."
          },
          "skipped": {
            "type": "number"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "imported",
          "updated",
          "skipped"
        ]
      },
      "GenericWebhookIntegration": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "webhookConfigured": {
            "type": "boolean"
          },
          "maskedWebhookUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "secretConfigured": {
            "type": "boolean"
          },
          "maskedSecret": {
            "type": [
              "string",
              "null"
            ],
            "description": "A preview of the HMAC signing secret used to sign outgoing deliveries."
          },
          "events": {
            "$ref": "#/components/schemas/GenericWebhookEvents"
          },
          "dueDateReminderLeadTimeMinutes": {
            "type": "number",
            "description": "How long before a due date the dueDateReminder event fires, in minutes."
          },
          "isActive": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "webhookConfigured",
          "maskedWebhookUrl",
          "secretConfigured",
          "maskedSecret",
          "events",
          "dueDateReminderLeadTimeMinutes",
          "isActive",
          "createdAt",
          "updatedAt"
        ]
      },
      "GenericWebhookEvents": {
        "allOf": [
          {
            "$ref": "#/components/schemas/IntegrationEvents"
          },
          {
            "type": "object",
            "properties": {
              "taskDeleted": {
                "type": "boolean"
              },
              "taskMoved": {
                "type": "boolean"
              },
              "taskDueDateChanged": {
                "type": "boolean"
              },
              "taskAssigneeChanged": {
                "type": "boolean"
              },
              "taskUnassigned": {
                "type": "boolean"
              },
              "dueDateReminder": {
                "type": "boolean"
              }
            },
            "required": [
              "taskDeleted",
              "taskMoved",
              "taskDueDateChanged",
              "taskAssigneeChanged",
              "taskUnassigned",
              "dueDateReminder"
            ]
          }
        ]
      },
      "IntegrationEvents": {
        "type": "object",
        "properties": {
          "taskCreated": {
            "type": "boolean"
          },
          "taskStatusChanged": {
            "type": "boolean"
          },
          "taskPriorityChanged": {
            "type": "boolean"
          },
          "taskTitleChanged": {
            "type": "boolean"
          },
          "taskDescriptionChanged": {
            "type": "boolean"
          },
          "taskCommentCreated": {
            "type": "boolean"
          }
        },
        "required": [
          "taskCreated",
          "taskStatusChanged",
          "taskPriorityChanged",
          "taskTitleChanged",
          "taskDescriptionChanged",
          "taskCommentCreated"
        ]
      },
      "DiscordIntegration": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "channelName": {
            "type": [
              "string",
              "null"
            ]
          },
          "webhookConfigured": {
            "type": "boolean"
          },
          "maskedWebhookUrl": {
            "type": "string",
            "description": "The webhook URL with its secret path segment masked, or an empty string when none is set."
          },
          "events": {
            "$ref": "#/components/schemas/IntegrationEvents"
          },
          "isActive": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "channelName",
          "webhookConfigured",
          "maskedWebhookUrl",
          "events",
          "isActive",
          "createdAt",
          "updatedAt"
        ]
      },
      "MattermostIntegration": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "channelName": {
            "type": [
              "string",
              "null"
            ]
          },
          "webhookConfigured": {
            "type": "boolean"
          },
          "maskedWebhookUrl": {
            "type": "string",
            "description": "The webhook URL with its secret path segment masked, or an empty string when none is set."
          },
          "events": {
            "$ref": "#/components/schemas/IntegrationEvents"
          },
          "isActive": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "channelName",
          "webhookConfigured",
          "maskedWebhookUrl",
          "events",
          "isActive",
          "createdAt",
          "updatedAt"
        ]
      },
      "SlackIntegration": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "channelName": {
            "type": [
              "string",
              "null"
            ]
          },
          "webhookConfigured": {
            "type": "boolean"
          },
          "maskedWebhookUrl": {
            "type": "string",
            "description": "The webhook URL with its secret path segment masked, or an empty string when none is set."
          },
          "events": {
            "$ref": "#/components/schemas/IntegrationEvents"
          },
          "isActive": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "channelName",
          "webhookConfigured",
          "maskedWebhookUrl",
          "events",
          "isActive",
          "createdAt",
          "updatedAt"
        ]
      },
      "TelegramIntegration": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "chatId": {
            "type": "string",
            "description": "The Telegram chat, group, or channel messages are sent to."
          },
          "threadId": {
            "type": [
              "number",
              "null"
            ],
            "description": "Forum topic id, when the target group uses topics."
          },
          "chatLabel": {
            "type": [
              "string",
              "null"
            ],
            "description": "A human-readable name for the chat, shown in the UI."
          },
          "botTokenConfigured": {
            "type": "boolean"
          },
          "maskedBotToken": {
            "type": "string"
          },
          "events": {
            "$ref": "#/components/schemas/IntegrationEvents"
          },
          "isActive": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "chatId",
          "threadId",
          "chatLabel",
          "botTokenConfigured",
          "maskedBotToken",
          "events",
          "isActive",
          "createdAt",
          "updatedAt"
        ]
      },
      "TaskRelationWithTasks": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TaskRelation"
          },
          {
            "type": "object",
            "properties": {
              "sourceTask": {
                "$ref": "#/components/schemas/RelatedTask"
              },
              "targetTask": {
                "$ref": "#/components/schemas/RelatedTask"
              }
            },
            "required": [
              "sourceTask",
              "targetTask"
            ]
          }
        ]
      },
      "RelatedTask": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "isCompleted": {
            "type": "boolean",
            "description": "Whether the task is in a final column of its own project."
          },
          "priority": {
            "type": [
              "string",
              "null"
            ]
          },
          "number": {
            "type": [
              "number",
              "null"
            ]
          },
          "projectId": {
            "type": "string"
          },
          "userId": {
            "type": [
              "string",
              "null"
            ]
          },
          "assigneeName": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "title",
          "status",
          "isCompleted",
          "priority",
          "number",
          "projectId",
          "userId",
          "assigneeName"
        ]
      },
      "TaskRelation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "sourceTaskId": {
            "type": "string"
          },
          "targetTaskId": {
            "type": "string"
          },
          "relationType": {
            "type": "string",
            "description": "How the two tasks relate: `subtask`, `blocks`, or `related`."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "sourceTaskId",
          "targetTaskId",
          "relationType",
          "createdAt"
        ]
      },
      "CreatedExternalLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "integrationId": {
            "type": [
              "string",
              "null"
            ]
          },
          "resourceType": {
            "type": "string",
            "description": "The resource kind: `url` for a manual link, or an integration resource such as `issue` or `pull_request`."
          },
          "externalId": {
            "type": "string",
            "description": "The provider's resource identifier, or the URL for a manual link."
          },
          "url": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "description": "Provider-specific payload, parsed from the stored JSON string. Null when the link has no metadata."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "taskId",
          "integrationId",
          "resourceType",
          "externalId",
          "url",
          "title",
          "createdAt",
          "updatedAt"
        ]
      },
      "DeletedExternalLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "ExternalLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "integrationId": {
            "type": [
              "string",
              "null"
            ]
          },
          "resourceType": {
            "type": "string",
            "description": "The resource kind: `url` for a manual link, or an integration resource such as `issue` or `pull_request`."
          },
          "externalId": {
            "type": "string",
            "description": "The provider's resource identifier, or the URL for a manual link."
          },
          "url": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "description": "Provider-specific payload, parsed from the stored JSON string. Null when the link has no metadata."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "integration": {
            "$ref": "#/components/schemas/ExternalLinkIntegration"
          }
        },
        "required": [
          "id",
          "taskId",
          "integrationId",
          "resourceType",
          "externalId",
          "url",
          "title",
          "createdAt",
          "updatedAt",
          "integration"
        ]
      },
      "ExternalLinkIntegration": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "type"
        ]
      },
      "WorkflowRule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "integrationType": {
            "type": "string",
            "description": "The integration that emits the event, e.g. `github`."
          },
          "eventType": {
            "type": "string",
            "description": "The Kaneo event that triggers the move: `branch_push`, `pr_opened`, `pr_merged`, `issue_opened`, `issue_closed` or `issue_reopened`."
          },
          "columnId": {
            "type": "string",
            "description": "The column tasks are moved to when the event fires."
          },
          "columnName": {
            "type": [
              "string",
              "null"
            ]
          },
          "columnSlug": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "integrationType",
          "eventType",
          "columnId",
          "columnName",
          "columnSlug",
          "createdAt",
          "updatedAt"
        ]
      },
      "WorkflowRuleRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "integrationType": {
            "type": "string"
          },
          "eventType": {
            "type": "string"
          },
          "columnId": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "integrationType",
          "eventType",
          "columnId",
          "createdAt",
          "updatedAt"
        ]
      },
      "PendingInvitation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "workspaceId": {
            "type": "string"
          },
          "workspaceName": {
            "type": "string"
          },
          "inviterName": {
            "type": "string"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "description": "Always `pending` here; expired and accepted ones are filtered out."
          }
        },
        "required": [
          "id",
          "email",
          "workspaceId",
          "workspaceName",
          "inviterName",
          "expiresAt",
          "createdAt",
          "status"
        ]
      },
      "InvitationDetails": {
        "type": "object",
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "True only when the invitation can still be accepted."
          },
          "invitation": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "workspaceName": {
                "type": "string"
              },
              "inviterName": {
                "type": "string"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time"
              },
              "status": {
                "type": "string"
              },
              "expired": {
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "email",
              "workspaceName",
              "inviterName",
              "expiresAt",
              "status",
              "expired"
            ],
            "description": "Omitted when the invitation does not exist, was already accepted, or was canceled -- the details are withheld rather than leaked."
          },
          "error": {
            "type": "string",
            "description": "Why the invitation is unusable, when valid is false."
          }
        },
        "required": [
          "valid"
        ]
      },
      "Workspace": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "logo": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "role": {
            "type": [
              "string",
              "null"
            ],
            "description": "The caller's workspace role: a built-in role (owner, admin, member, viewer) or the name of a custom role defined in the workspace. Several roles are comma-separated, for example owner,viewer. Null when an instance admin views a workspace they are not a member of."
          }
        },
        "required": [
          "id",
          "name",
          "slug",
          "logo",
          "description",
          "createdAt",
          "role"
        ]
      },
      "WorkspaceMember": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "type": "string",
            "description": "The member's workspace role: a built-in role (owner, admin, member, viewer) or the name of a custom role defined in the workspace. Several roles are comma-separated, for example owner,viewer."
          }
        },
        "required": [
          "id",
          "name",
          "email",
          "image",
          "role"
        ]
      },
      "MemberProjectAccess": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string"
          },
          "projectAccess": {
            "type": "string",
            "enum": [
              "all",
              "selected"
            ]
          },
          "projectIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "userId",
          "projectAccess",
          "projectIds"
        ]
      },
      "WorkspaceCapabilities": {
        "type": "object",
        "properties": {
          "manageProjects": {
            "type": "boolean"
          },
          "createProjects": {
            "type": "boolean"
          },
          "updateProjects": {
            "type": "boolean"
          },
          "shareProjects": {
            "type": "boolean"
          },
          "deleteProjects": {
            "type": "boolean"
          },
          "updateTasks": {
            "type": "boolean"
          },
          "createTasks": {
            "type": "boolean"
          },
          "deleteTasks": {
            "type": "boolean"
          },
          "assignTasks": {
            "type": "boolean"
          },
          "createLabels": {
            "type": "boolean"
          },
          "updateLabels": {
            "type": "boolean"
          },
          "deleteLabels": {
            "type": "boolean"
          },
          "manageWorkspace": {
            "type": "boolean"
          },
          "manageSettings": {
            "type": "boolean"
          },
          "deleteWorkspace": {
            "type": "boolean"
          },
          "inviteUsers": {
            "type": "boolean"
          },
          "manageTeam": {
            "type": "boolean"
          },
          "updateMembers": {
            "type": "boolean"
          },
          "removeMembers": {
            "type": "boolean"
          }
        },
        "required": [
          "manageProjects",
          "createProjects",
          "updateProjects",
          "shareProjects",
          "deleteProjects",
          "updateTasks",
          "createTasks",
          "deleteTasks",
          "assignTasks",
          "createLabels",
          "updateLabels",
          "deleteLabels",
          "manageWorkspace",
          "manageSettings",
          "deleteWorkspace",
          "inviteUsers",
          "manageTeam",
          "updateMembers",
          "removeMembers"
        ],
        "description": "Whether the caller's roles (and API key scope, if any) allow each named action in the workspace. Mirrors the checks the API enforces."
      },
      "CustomFieldDefinition": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "required": {
            "type": "boolean"
          },
          "defaultValue": {
            "type": [
              "string",
              "null"
            ]
          },
          "options": {},
          "position": {
            "type": "number"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "projectId",
          "name",
          "type",
          "required",
          "defaultValue",
          "position",
          "createdAt",
          "updatedAt"
        ]
      },
      "CustomFieldValue": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "fieldId": {
            "type": "string"
          },
          "value": {
            "type": [
              "string",
              "null"
            ]
          },
          "fieldName": {
            "type": "string"
          },
          "fieldPosition": {
            "type": "number"
          },
          "fieldType": {
            "type": "string"
          },
          "fieldOptions": {}
        },
        "required": [
          "id",
          "taskId",
          "fieldId",
          "value",
          "fieldName",
          "fieldPosition",
          "fieldType"
        ]
      },
      "CustomFieldFilterValues": {
        "type": "object",
        "properties": {
          "fieldId": {
            "type": "string"
          },
          "fieldName": {
            "type": "string"
          },
          "fieldType": {
            "type": "string"
          },
          "values": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "fieldId",
          "fieldName",
          "fieldType",
          "values"
        ]
      },
      "ReorderCustomFieldsResponse": {},
      "SetCustomFieldValueResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          },
          "fieldId": {
            "type": "string"
          },
          "value": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "taskId",
          "fieldId",
          "value"
        ]
      },
      "ImportSourceProjectList": {
        "type": "object",
        "properties": {
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImportSourceProject"
            }
          }
        },
        "required": [
          "projects"
        ]
      },
      "ImportSourceProject": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "detail": {
            "type": "string",
            "description": "Extra context shown next to the name, such as the source workspace or team."
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "ImportConnection": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/PlankaImportConnection"
          },
          {
            "$ref": "#/components/schemas/PlaneImportConnection"
          },
          {
            "$ref": "#/components/schemas/LinearImportConnection"
          },
          {
            "$ref": "#/components/schemas/JiraImportConnection"
          },
          {
            "$ref": "#/components/schemas/TrelloImportConnection"
          },
          {
            "$ref": "#/components/schemas/KaneoImportConnection"
          }
        ],
        "discriminator": {
          "propertyName": "source",
          "mapping": {
            "planka": "#/components/schemas/PlankaImportConnection",
            "plane": "#/components/schemas/PlaneImportConnection",
            "linear": "#/components/schemas/LinearImportConnection",
            "jira": "#/components/schemas/JiraImportConnection",
            "trello": "#/components/schemas/TrelloImportConnection",
            "kaneo": "#/components/schemas/KaneoImportConnection"
          }
        },
        "description": "Where to import from and how to sign in. Credentials are used only for this request and the import it starts; they are never stored, logged, or returned."
      },
      "PlankaImportConnection": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "planka"
            ]
          },
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Address of the PLANKA instance.",
            "example": "https://planka.example.com"
          },
          "apiKey": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4096,
            "description": "A PLANKA API key. Send it, or username and password. Used only for this import and never stored or logged."
          },
          "username": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "description": "Username or email of a PLANKA account, used with password when no API key is sent."
          },
          "password": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4096,
            "description": "Password of that PLANKA account. Used only for this import and never stored or logged."
          }
        },
        "required": [
          "source",
          "url"
        ]
      },
      "PlaneImportConnection": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "plane"
            ]
          },
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Address of the Plane instance, e.g. https://app.plane.so for Plane Cloud.",
            "example": "https://planka.example.com"
          },
          "apiKey": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4096,
            "description": "A Plane personal access token. Used only for this import and never stored or logged."
          },
          "workspaceSlug": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "description": "Slug of the Plane workspace to read. Optional when the URL already names it."
          }
        },
        "required": [
          "source",
          "url",
          "apiKey"
        ]
      },
      "LinearImportConnection": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "linear"
            ]
          },
          "apiKey": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4096,
            "description": "A Linear personal API key. Used only for this import and never stored or logged."
          }
        },
        "required": [
          "source",
          "apiKey"
        ]
      },
      "JiraImportConnection": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "jira"
            ]
          },
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Address of the Jira site, e.g. https://your-site.atlassian.net for Jira Cloud, or your own domain for Jira Data Center.",
            "example": "https://your-site.atlassian.net"
          },
          "email": {
            "type": "string",
            "minLength": 1,
            "maxLength": 320,
            "description": "Email of the Atlassian account the API token belongs to. Required for Jira Cloud; leave it out for Jira Data Center."
          },
          "apiToken": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4096,
            "description": "A Jira Cloud API token, sent with email, or a Jira Data Center personal access token, sent without email. Used only for this import and never stored or logged."
          }
        },
        "required": [
          "source",
          "url",
          "apiToken"
        ]
      },
      "TrelloImportConnection": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "trello"
            ]
          },
          "token": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4096,
            "description": "A Trello token authorized for this Kaneo instance. Used only for this import and never stored or logged."
          },
          "apiKey": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "description": "Trello API key, a public app identifier. Defaults to the server's TRELLO_API_KEY."
          }
        },
        "required": [
          "source",
          "token"
        ]
      },
      "KaneoImportConnection": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "kaneo"
            ]
          },
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Address of the Kaneo instance to import from.",
            "example": "https://planka.example.com"
          },
          "apiKey": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4096,
            "description": "An API key from that Kaneo instance. Used only for this import and never stored or logged."
          }
        },
        "required": [
          "source",
          "url",
          "apiKey"
        ]
      },
      "ImportRunEnvelope": {
        "type": "object",
        "properties": {
          "run": {
            "$ref": "#/components/schemas/ImportRun"
          }
        },
        "required": [
          "run"
        ]
      },
      "ImportRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "workspaceId": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "planka",
              "plane",
              "linear",
              "jira",
              "trello",
              "kaneo"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "completed",
              "failed"
            ],
            "description": "completed once every selected project was attempted, even if some failed; check each project's status. failed means the run itself stopped."
          },
          "includeComments": {
            "type": "boolean"
          },
          "message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Latest progress line while the run is running."
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImportRunProject"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "finishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "workspaceId",
          "source",
          "status",
          "includeComments",
          "message",
          "error",
          "projects",
          "createdAt",
          "finishedAt"
        ]
      },
      "ImportRunProject": {
        "type": "object",
        "properties": {
          "sourceId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "completed",
              "failed"
            ]
          },
          "projectId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The Kaneo project created by the import, once completed."
          },
          "projectSlug": {
            "type": [
              "string",
              "null"
            ]
          },
          "counts": {
            "$ref": "#/components/schemas/ImportCounts"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "sourceId",
          "name",
          "status",
          "projectId",
          "projectSlug",
          "counts",
          "warnings",
          "error"
        ]
      },
      "ImportCounts": {
        "type": "object",
        "properties": {
          "columns": {
            "type": "integer"
          },
          "tasks": {
            "type": "integer"
          },
          "labels": {
            "type": "integer"
          },
          "comments": {
            "type": "integer"
          },
          "assignees": {
            "type": "integer",
            "description": "Tasks that were assigned to a workspace member."
          },
          "relations": {
            "type": "integer"
          }
        },
        "required": [
          "columns",
          "tasks",
          "labels",
          "comments",
          "assignees",
          "relations"
        ]
      },
      "ImportRunList": {
        "type": "object",
        "properties": {
          "runs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImportRun"
            }
          }
        },
        "required": [
          "runs"
        ]
      },
      "TrelloAuthorizeUrl": {
        "type": "object",
        "properties": {
          "authorizeUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Trello page where the user approves read access for Kaneo and copies a token. null when the server has no TRELLO_API_KEY; the user then sends their own Trello API key with the token."
          }
        },
        "required": [
          "authorizeUrl"
        ]
      },
      "CurrentUser": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "email",
          "image",
          "role"
        ]
      },
      "UserAvatar": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Public URL for the stored avatar, served from /api/user/avatar/{id}."
          },
          "size": {
            "type": "number",
            "description": "Decoded size in bytes."
          }
        },
        "required": [
          "id",
          "url",
          "size"
        ]
      },
      "UserAvatarDeleted": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "boolean",
            "description": "False when the user had no uploaded avatar to remove."
          }
        },
        "required": [
          "deleted"
        ]
      },
      "AdminUserList": {
        "type": "object",
        "properties": {
          "users": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminUser"
            }
          },
          "total": {
            "type": "integer",
            "description": "Number of users matching the search, across all pages."
          }
        },
        "required": [
          "users",
          "total"
        ]
      },
      "AdminUser": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "emailVerified": {
            "type": "boolean"
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "role": {
            "type": [
              "string",
              "null"
            ]
          },
          "banned": {
            "type": "boolean"
          },
          "banReason": {
            "type": [
              "string",
              "null"
            ]
          },
          "banExpires": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name",
          "email",
          "emailVerified",
          "image",
          "createdAt",
          "updatedAt",
          "role",
          "banned",
          "banReason",
          "banExpires"
        ]
      },
      "AdminWorkspaceList": {
        "type": "object",
        "properties": {
          "workspaces": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminWorkspace"
            }
          },
          "total": {
            "type": "integer",
            "description": "Number of workspaces matching the search, across all pages."
          }
        },
        "required": [
          "workspaces",
          "total"
        ]
      },
      "AdminWorkspace": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "memberCount": {
            "type": "integer"
          },
          "projectCount": {
            "type": "integer"
          },
          "owners": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "email": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name",
                "email"
              ]
            }
          }
        },
        "required": [
          "id",
          "name",
          "slug",
          "createdAt",
          "memberCount",
          "projectCount",
          "owners"
        ]
      },
      "AdminWorkspaceMember": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "type": "string"
          },
          "joinedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "userId",
          "name",
          "email",
          "image",
          "role",
          "joinedAt"
        ]
      }
    },
    "parameters": {}
  },
  "paths": {
    "/instance/status": {
      "get": {
        "operationId": "getInstanceStatus",
        "tags": [
          "Instance"
        ],
        "summary": "Get instance status",
        "description": "Public instance setup status. When hasUsers is false the next signup becomes the instance admin.",
        "security": [],
        "responses": {
          "200": {
            "description": "Instance status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceStatus"
                }
              }
            }
          }
        }
      }
    },
    "/public-project/{id}": {
      "get": {
        "operationId": "getPublicProject",
        "tags": [
          "Projects"
        ],
        "summary": "Get a public project board",
        "description": "Read a public board in bounded task pages. Visibility is checked before loading tasks. Continue through pagination.totalPages for all tasks and through relatedPage/pagination.relatedTotalPages for their complete related records.",
        "security": [],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "priority",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "assigneeId",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000
            },
            "required": false,
            "name": "relatedPage",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "createdAt",
                "priority",
                "dueDate",
                "position",
                "title",
                "number"
              ]
            },
            "required": false,
            "name": "sortBy",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "required": false,
            "name": "sortOrder",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "dueBefore",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "dueAfter",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "A public board page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicBoardPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid pagination or filters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Project is not public",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "Task list request timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/public-project/{id}/description": {
      "get": {
        "operationId": "getPublicProjectDescriptionPage",
        "tags": [
          "Projects"
        ],
        "summary": "Read a public project description page",
        "security": [],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 2000000000,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,10}$"
            },
            "required": false,
            "name": "version",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Public project description page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskDescriptionPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid description cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Public project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Description changed or no longer public",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "Description request timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/public-project/{id}/task/{taskId}/description": {
      "get": {
        "operationId": "getPublicTaskDescriptionPage",
        "tags": [
          "Projects"
        ],
        "summary": "Read a public task description page",
        "security": [],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 2000000000,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,10}$"
            },
            "required": false,
            "name": "version",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Public task description page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskDescriptionPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid description cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Public task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Description changed or no longer public",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "Description request timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/calendar-feed/{token}/calendar.ics": {
      "get": {
        "operationId": "getCalendarFeed",
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Subscribe to a calendar feed",
        "description": "Read scheduled project tasks using a secret calendar feed link. Anyone with the link can read matching task titles, descriptions, and dates. Responses are streamed; descriptions longer than 4096 characters and titles or calendar names longer than 1024 characters are truncated with an ellipsis.",
        "security": [],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{64}$"
            },
            "required": true,
            "name": "token",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar feed",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid feed token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Calendar feed not found or revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/get-session": {
      "get": {
        "operationId": "getSession",
        "tags": [
          "Authentication"
        ],
        "summary": "Get session",
        "description": "Get the current authenticated session, or null when the caller is not signed in. Served by Better Auth.",
        "security": [],
        "responses": {
          "200": {
            "description": "Current session details, or null when unauthenticated"
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/asset/{id}": {
      "get": {
        "operationId": "getAsset",
        "tags": [
          "Assets"
        ],
        "summary": "Download asset",
        "description": "Download an uploaded asset. Readable without signing in only when it belongs to a public project; image types are served inline, everything else as an attachment.",
        "security": [],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The requested asset binary stream",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          },
          "403": {
            "description": "Staged upload owned by another user, or no access to the asset's project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Asset not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/user/avatar/{id}": {
      "get": {
        "operationId": "getUserAvatar",
        "tags": [
          "User"
        ],
        "summary": "Download avatar",
        "description": "Download a user avatar by its avatar ID. Public, immutable, and cache-friendly: the id changes whenever the avatar is replaced.",
        "security": [],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The avatar image",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          },
          "404": {
            "description": "Avatar not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/config": {
      "get": {
        "operationId": "getConfig",
        "tags": [
          "Config"
        ],
        "summary": "Get config",
        "description": "Public instance settings: which sign-in methods, registration paths, and features are enabled.",
        "security": [],
        "responses": {
          "200": {
            "description": "Application settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Config"
                }
              }
            }
          }
        }
      }
    },
    "/auth/organization/accept-invitation": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "acceptOrganizationInvitation",
        "summary": "Accept Organization Invitation",
        "description": "Accept an invitation to an organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invitationId": {
                    "type": "string",
                    "description": "The ID of the invitation to accept"
                  }
                },
                "required": [
                  "invitationId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invitation": {
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "member": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/add-team-member": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "addOrganizationTeamMember",
        "summary": "Add Organization Team Member",
        "description": "The newly created member",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "teamId": {
                    "type": "string",
                    "description": "The team the user should be a member of."
                  },
                  "userId": {
                    "type": "string",
                    "description": "The user Id which represents the user to be added as a member."
                  },
                  "organizationId": {
                    "type": "string",
                    "description": "The organization ID which the team falls under. If not provided, it will default to the user's active organization."
                  }
                },
                "required": [
                  "teamId",
                  "userId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Unique identifier of the team member"
                    },
                    "userId": {
                      "type": "string",
                      "description": "The user ID of the team member"
                    },
                    "teamId": {
                      "type": "string",
                      "description": "The team ID of the team the team member is in"
                    },
                    "createdAt": {
                      "type": "string",
                      "description": "Timestamp when the team member was created"
                    }
                  },
                  "required": [
                    "id",
                    "userId",
                    "teamId",
                    "createdAt"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/cancel-invitation": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "cancelOrganizationInvitation",
        "summary": "Cancel Organization Invitation",
        "description": "Cancel Organization Invitation",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invitationId": {
                    "type": "string",
                    "description": "The ID of the invitation to cancel"
                  }
                },
                "required": [
                  "invitationId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/check-slug": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "checkOrganizationSlug",
        "summary": "Check Organization Slug",
        "description": "Check Organization Slug",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "description": "The organization slug to check. Eg: \"my-org\""
                  }
                },
                "required": [
                  "slug"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/create": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "createOrganization",
        "summary": "Create Organization",
        "description": "Create an organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The name of the organization"
                  },
                  "slug": {
                    "type": "string",
                    "description": "The slug of the organization"
                  },
                  "userId": {
                    "type": "string",
                    "description": "The user id of the organization creator. If not provided, the current user will be used. Should only be used by admins or when called by the server. server-only. Eg: \"user-id\""
                  },
                  "logo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The logo of the organization"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {},
                    "description": "The metadata of the organization"
                  },
                  "keepCurrentActiveOrganization": {
                    "type": "boolean",
                    "description": "Whether to keep the current active organization active after creating a new one. Eg: true"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "name",
                  "slug"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/create-role": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "createOrganizationRole",
        "summary": "Create Organization Role",
        "description": "Create Organization Role",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "organizationId": {
                    "type": "string"
                  },
                  "role": {
                    "type": "string",
                    "description": "The name of the role to create"
                  },
                  "permission": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "description": "The permission to assign to the role"
                  },
                  "additionalFields": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "role",
                  "permission"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/create-team": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "createOrganizationTeam",
        "summary": "Create Organization Team",
        "description": "Create a new team within an organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The name of the team. Eg: \"my-team\""
                  },
                  "organizationId": {
                    "type": "string",
                    "description": "The organization ID which the team will be created in. Defaults to the active organization. Eg: \"organization-id\""
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Unique identifier of the created team"
                    },
                    "name": {
                      "type": "string",
                      "description": "Name of the team"
                    },
                    "organizationId": {
                      "type": "string",
                      "description": "ID of the organization the team belongs to"
                    },
                    "createdAt": {
                      "type": "string",
                      "description": "Timestamp when the team was created"
                    },
                    "updatedAt": {
                      "type": "string",
                      "description": "Timestamp when the team was last updated"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "organizationId",
                    "createdAt",
                    "updatedAt"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/delete": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "deleteOrganization",
        "summary": "Delete Organization",
        "description": "Delete an organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "organizationId": {
                    "type": "string",
                    "description": "The organization id to delete"
                  }
                },
                "required": [
                  "organizationId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/delete-role": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "deleteOrganizationRole",
        "summary": "Delete Organization Role",
        "description": "Delete Organization Role",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "type": "object",
                    "properties": {
                      "organizationId": {
                        "type": "string"
                      }
                    }
                  },
                  {
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "roleName": {
                            "type": "string",
                            "description": "The name of the role to delete"
                          }
                        },
                        "required": [
                          "roleName"
                        ]
                      },
                      {
                        "type": "object",
                        "properties": {
                          "roleId": {
                            "type": "string",
                            "description": "The id of the role to delete"
                          }
                        },
                        "required": [
                          "roleId"
                        ]
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/get-active-member": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "getOrganizationActiveMember",
        "summary": "Get Organization Active Member",
        "description": "Get the member details of the active organization",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "userId": {
                      "type": "string"
                    },
                    "organizationId": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "userId",
                    "organizationId",
                    "role"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/get-active-member-role": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "getOrganizationActiveMemberRole",
        "summary": "Get Organization Active Member Role",
        "description": "Get Organization Active Member Role",
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/get-full-organization": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "getOrganizationFullOrganization",
        "summary": "Get Organization Full Organization",
        "description": "Get the full organization",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/get-invitation": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "getOrganizationInvitation",
        "summary": "Get Organization Invitation",
        "description": "Get an invitation by ID",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string"
                    },
                    "organizationId": {
                      "type": "string"
                    },
                    "inviterId": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "expiresAt": {
                      "type": "string"
                    },
                    "organizationName": {
                      "type": "string"
                    },
                    "organizationSlug": {
                      "type": "string"
                    },
                    "inviterEmail": {
                      "type": "string"
                    },
                    "projectAccess": {
                      "type": "string",
                      "enum": [
                        "all",
                        "selected"
                      ],
                      "description": "\"all\" gives access to every project once accepted; \"selected\" limits the member to projectIds."
                    },
                    "projectIds": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Projects the invitation grants when projectAccess is \"selected\". Only projects the viewer can access are listed."
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "role",
                    "organizationId",
                    "inviterId",
                    "status",
                    "expiresAt",
                    "organizationName",
                    "organizationSlug",
                    "inviterEmail",
                    "projectAccess",
                    "projectIds"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/get-role": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "getOrganizationRole",
        "summary": "Get Organization Role",
        "description": "Get Organization Role",
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/has-permission": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "hasOrganizationPermission",
        "summary": "Check Organization Permission",
        "description": "Check if the user has permission",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "permission": {
                    "type": "object",
                    "additionalProperties": {},
                    "description": "The permission to check"
                  },
                  "permissions": {
                    "type": "object",
                    "additionalProperties": {},
                    "description": "The permission to check"
                  }
                },
                "required": [
                  "permissions"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/invite-member": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "inviteOrganizationMember",
        "summary": "Invite Organization Member",
        "description": "Create or resend an invitation. Email delivery failures return 502; the invitation remains pending and can be resent after correcting SMTP settings.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "The email address of the user to invite"
                  },
                  "role": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    ],
                    "description": "The role(s) to assign to the user. It can be `admin`, `member`, owner. Eg: \"member\""
                  },
                  "organizationId": {
                    "type": "string",
                    "description": "The organization ID to invite the user to"
                  },
                  "resend": {
                    "type": "boolean",
                    "description": "Resend the invitation email, if the user is already invited. Eg: true"
                  },
                  "teamId": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    ]
                  },
                  "projectAccess": {
                    "type": "string",
                    "enum": [
                      "all",
                      "selected"
                    ],
                    "description": "\"all\" (the default) gives access to every project. \"selected\" limits the new member to projectIds once they accept."
                  },
                  "projectIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Projects the new member can access when projectAccess is \"selected\". They must belong to the workspace, and an inviter whose own access is limited can only choose projects they can access."
                  }
                },
                "required": [
                  "email",
                  "role"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string"
                    },
                    "organizationId": {
                      "type": "string"
                    },
                    "inviterId": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "expiresAt": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string"
                    },
                    "projectAccess": {
                      "type": "string",
                      "enum": [
                        "all",
                        "selected"
                      ],
                      "description": "\"all\" gives access to every project once accepted; \"selected\" limits the member to projectIds."
                    },
                    "projectIds": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Projects the invitation grants when projectAccess is \"selected\". Only projects the viewer can access are listed."
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "role",
                    "organizationId",
                    "inviterId",
                    "status",
                    "expiresAt",
                    "createdAt",
                    "projectAccess",
                    "projectIds"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "502": {
            "description": "Invitation saved, but email delivery failed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "enum": [
                        "INVITATION_EMAIL_FAILED"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "code",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/auth/organization/leave": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "leaveOrganization",
        "summary": "Leave Organization",
        "description": "Leave Organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "organizationId": {
                    "type": "string",
                    "description": "The organization Id for the member to leave. Eg: \"organization-id\""
                  }
                },
                "required": [
                  "organizationId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/list": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "listOrganization",
        "summary": "List Organization",
        "description": "List all organizations",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {}
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/list-invitations": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "listOrganizationInvitations",
        "summary": "List Organization Invitations",
        "description": "List Organization Invitations",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "organizationId",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "email": {
                        "type": "string"
                      },
                      "role": {
                        "type": "string"
                      },
                      "organizationId": {
                        "type": "string"
                      },
                      "inviterId": {
                        "type": "string"
                      },
                      "status": {
                        "type": "string"
                      },
                      "expiresAt": {
                        "type": "string"
                      },
                      "createdAt": {
                        "type": "string"
                      },
                      "projectAccess": {
                        "type": "string",
                        "enum": [
                          "all",
                          "selected"
                        ],
                        "description": "\"all\" gives access to every project once accepted; \"selected\" limits the member to projectIds."
                      },
                      "projectIds": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "description": "Projects the invitation grants when projectAccess is \"selected\". Only projects the viewer can access are listed."
                      }
                    },
                    "required": [
                      "id",
                      "email",
                      "role",
                      "organizationId",
                      "inviterId",
                      "status",
                      "expiresAt",
                      "createdAt",
                      "projectAccess",
                      "projectIds"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/list-members": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "listOrganizationMembers",
        "summary": "List Organization Members",
        "description": "List Organization Members",
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/list-roles": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "listOrganizationRoles",
        "summary": "List Organization Roles",
        "description": "List Organization Roles",
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/list-team-members": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "listOrganizationTeamMembers",
        "summary": "List Organization Team Members",
        "description": "List the members of the given team.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "description": "Unique identifier of the team member"
                      },
                      "userId": {
                        "type": "string",
                        "description": "The user ID of the team member"
                      },
                      "teamId": {
                        "type": "string",
                        "description": "The team ID of the team the team member is in"
                      },
                      "createdAt": {
                        "type": "string",
                        "description": "Timestamp when the team member was created"
                      }
                    },
                    "required": [
                      "id",
                      "userId",
                      "teamId",
                      "createdAt"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/list-teams": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "listOrganizationTeams",
        "summary": "List Organization Teams",
        "description": "List all teams in an organization",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "description": "Unique identifier of the team"
                      },
                      "name": {
                        "type": "string",
                        "description": "Name of the team"
                      },
                      "organizationId": {
                        "type": "string",
                        "description": "ID of the organization the team belongs to"
                      },
                      "createdAt": {
                        "type": "string",
                        "description": "Timestamp when the team was created"
                      },
                      "updatedAt": {
                        "type": "string",
                        "description": "Timestamp when the team was last updated"
                      }
                    },
                    "required": [
                      "id",
                      "name",
                      "organizationId",
                      "createdAt",
                      "updatedAt"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/list-user-invitations": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "listOrganizationUserInvitations",
        "summary": "List Organization User Invitations",
        "description": "List all invitations a user has received",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "email": {
                        "type": "string"
                      },
                      "role": {
                        "type": "string"
                      },
                      "organizationId": {
                        "type": "string"
                      },
                      "organizationName": {
                        "type": "string"
                      },
                      "inviterId": {
                        "type": "string",
                        "description": "The ID of the user who created the invitation"
                      },
                      "teamId": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "The ID of the team associated with the invitation"
                      },
                      "status": {
                        "type": "string"
                      },
                      "expiresAt": {
                        "type": "string"
                      },
                      "createdAt": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "id",
                      "email",
                      "role",
                      "organizationId",
                      "organizationName",
                      "inviterId",
                      "status",
                      "expiresAt",
                      "createdAt"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/list-user-teams": {
      "get": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "listOrganizationUserTeams",
        "summary": "List Organization User Teams",
        "description": "List all teams that the current user is a part of.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {}
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/reject-invitation": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "rejectOrganizationInvitation",
        "summary": "Reject Organization Invitation",
        "description": "Reject an invitation to an organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invitationId": {
                    "type": "string",
                    "description": "The ID of the invitation to reject"
                  }
                },
                "required": [
                  "invitationId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invitation": {
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "member": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": {}
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/remove-member": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "removeOrganizationMember",
        "summary": "Remove Organization Member",
        "description": "Remove a member from an organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "memberIdOrEmail": {
                    "type": "string",
                    "description": "The ID or email of the member to remove"
                  },
                  "organizationId": {
                    "type": "string",
                    "description": "The ID of the organization to remove the member from. If not provided, the active organization will be used. Eg: \"org-id\""
                  }
                },
                "required": [
                  "memberIdOrEmail"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "member": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "userId": {
                          "type": "string"
                        },
                        "organizationId": {
                          "type": "string"
                        },
                        "role": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "userId",
                        "organizationId",
                        "role"
                      ]
                    }
                  },
                  "required": [
                    "member"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/remove-team": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "removeOrganizationTeam",
        "summary": "Remove Organization Team",
        "description": "Remove a team from an organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "teamId": {
                    "type": "string",
                    "description": "The team ID of the team to remove. Eg: \"team-id\""
                  },
                  "organizationId": {
                    "type": "string",
                    "description": "The organization ID which the team falls under. If not provided, it will default to the user's active organization. Eg: \"organization-id\""
                  }
                },
                "required": [
                  "teamId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "enum": [
                        "Team removed successfully."
                      ],
                      "description": "Confirmation message indicating successful removal"
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/remove-team-member": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "removeOrganizationTeamMember",
        "summary": "Remove Organization Team Member",
        "description": "Remove a member from a team",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "teamId": {
                    "type": "string",
                    "description": "The team the user should be removed from."
                  },
                  "userId": {
                    "type": "string",
                    "description": "The user which should be removed from the team."
                  },
                  "organizationId": {
                    "type": "string",
                    "description": "The organization ID which the team falls under. If not provided, it will default to the user's active organization."
                  }
                },
                "required": [
                  "teamId",
                  "userId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "enum": [
                        "Team member removed successfully."
                      ],
                      "description": "Confirmation message indicating successful removal"
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/set-active": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "setOrganizationActive",
        "summary": "Set Organization Active",
        "description": "Set the active organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "organizationId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The organization id to set as active. It can be null to unset the active organization. Eg: \"org-id\""
                  },
                  "organizationSlug": {
                    "type": "string",
                    "description": "The organization slug to set as active. It can be null to unset the active organization if organizationId is not provided. Eg: \"org-slug\""
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/set-active-team": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "setOrganizationActiveTeam",
        "summary": "Set Organization Active Team",
        "description": "Set the active team for the current active organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "teamId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The team id to set as active. It can be null to unset the active team"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/update": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "updateOrganization",
        "summary": "Update Organization",
        "description": "Update an organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "type": "object",
                    "properties": {
                      "description": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "name": {
                        "type": "string",
                        "description": "The name of the organization"
                      },
                      "slug": {
                        "type": "string",
                        "description": "The slug of the organization"
                      },
                      "logo": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "The logo of the organization"
                      },
                      "metadata": {
                        "type": "object",
                        "additionalProperties": {},
                        "description": "The metadata of the organization"
                      }
                    }
                  },
                  "organizationId": {
                    "type": "string",
                    "description": "The organization ID. Eg: \"org-id\""
                  }
                },
                "required": [
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/update-member-role": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "updateOrganizationMemberRole",
        "summary": "Update Organization Member Role",
        "description": "Update the role of a member in an organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    ],
                    "description": "The new role to be applied. This can be a string or array of strings representing the roles. Eg: [\"admin\", \"sale\"]"
                  },
                  "memberId": {
                    "type": "string",
                    "description": "The member id to apply the role update to. Eg: \"member-id\""
                  },
                  "organizationId": {
                    "type": "string",
                    "description": "An optional organization ID which the member is a part of to apply the role update. If not provided, you must provide session headers to get the active organization. Eg: \"organization-id\""
                  }
                },
                "required": [
                  "role",
                  "memberId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "member": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "userId": {
                          "type": "string"
                        },
                        "organizationId": {
                          "type": "string"
                        },
                        "role": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "userId",
                        "organizationId",
                        "role"
                      ]
                    }
                  },
                  "required": [
                    "member"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/update-role": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "updateOrganizationRole",
        "summary": "Update Organization Role",
        "description": "Update Organization Role",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "type": "object",
                    "properties": {
                      "organizationId": {
                        "type": "string"
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "permission": {
                            "type": "object",
                            "additionalProperties": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          },
                          "roleName": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "required": [
                      "data"
                    ]
                  },
                  {
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "roleName": {
                            "type": "string",
                            "description": "The name of the role to update"
                          }
                        },
                        "required": [
                          "roleName"
                        ]
                      },
                      {
                        "type": "object",
                        "properties": {
                          "roleId": {
                            "type": "string",
                            "description": "The id of the role to update"
                          }
                        },
                        "required": [
                          "roleId"
                        ]
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/organization/update-team": {
      "post": {
        "tags": [
          "Organization Management"
        ],
        "operationId": "updateOrganizationTeam",
        "summary": "Update Organization Team",
        "description": "Update an existing team in an organization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "teamId": {
                    "type": "string",
                    "description": "The ID of the team to be updated. Eg: \"team-id\""
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      },
                      "organizationId": {
                        "type": "string"
                      },
                      "createdAt": {
                        "type": "string"
                      },
                      "updatedAt": {
                        "type": "string"
                      }
                    }
                  }
                },
                "required": [
                  "teamId",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Unique identifier of the updated team"
                    },
                    "name": {
                      "type": "string",
                      "description": "Updated name of the team"
                    },
                    "organizationId": {
                      "type": "string",
                      "description": "ID of the organization the team belongs to"
                    },
                    "createdAt": {
                      "type": "string",
                      "description": "Timestamp when the team was created"
                    },
                    "updatedAt": {
                      "type": "string",
                      "description": "Timestamp when the team was last updated"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "organizationId",
                    "createdAt",
                    "updatedAt"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/auth/device": {
      "get": {
        "operationId": "getDeviceAuthorizationPage",
        "tags": [
          "Authentication"
        ],
        "summary": "Device authorization page",
        "description": "Better Auth serves this as JSON. A top-level browser navigation is redirected to the web app's device screen instead, so opening the URL by hand shows a page rather than a JSON blob.",
        "security": [],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The device authorization user code."
            },
            "required": false,
            "description": "The device authorization user code.",
            "name": "user_code",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ],
              "description": "Force a redirect to the web UI, for clients that do not send Sec-Fetch-* headers."
            },
            "required": false,
            "description": "Force a redirect to the web UI, for clients that do not send Sec-Fetch-* headers.",
            "name": "ui",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Device authorization payload from Better Auth"
          },
          "302": {
            "description": "Redirects the browser to the web app device screen"
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/mcp/register": {
      "post": {
        "operationId": "registerMcpOAuthClient",
        "tags": [
          "MCP"
        ],
        "summary": "Register MCP OAuth client",
        "description": "Dynamically register a public OAuth client for the MCP endpoint. Public clients hold no secret, so authorization is protected by PKCE and an explicit consent step.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "redirect_uris": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 2048
                    },
                    "minItems": 1,
                    "maxItems": 10
                  },
                  "client_name": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "token_endpoint_auth_method": {
                    "type": "string",
                    "enum": [
                      "none"
                    ]
                  },
                  "grant_types": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "authorization_code",
                        "refresh_token"
                      ]
                    },
                    "maxItems": 2
                  },
                  "response_types": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "code"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 1
                  }
                },
                "required": [
                  "redirect_uris"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registered OAuth client",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpClientRegistration"
                }
              }
            }
          },
          "400": {
            "description": "Invalid client metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/mcp/authorize": {
      "get": {
        "operationId": "authorizeMcpOAuthClient",
        "tags": [
          "MCP"
        ],
        "summary": "Start MCP authorization",
        "description": "Begin an MCP OAuth authorization. Redirects the browser to the Kaneo consent page, which then approves or denies the request.",
        "security": [],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "code"
              ]
            },
            "required": true,
            "name": "response_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            },
            "required": true,
            "name": "client_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 2048
            },
            "required": true,
            "name": "redirect_uri",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$",
              "description": "PKCE challenge; only S256 is accepted."
            },
            "required": true,
            "description": "PKCE challenge; only S256 is accepted.",
            "name": "code_challenge",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "S256"
              ]
            },
            "required": true,
            "name": "code_challenge_method",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Opaque client state, returned unchanged. The complete authorization URL is limited to 8 KiB."
            },
            "required": false,
            "description": "Opaque client state, returned unchanged. The complete authorization URL is limited to 8 KiB.",
            "name": "state",
            "in": "query"
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the Kaneo consent page"
          },
          "400": {
            "description": "Invalid authorization request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/mcp/authorize/request/{requestId}": {
      "get": {
        "operationId": "getMcpAuthorizationRequest",
        "tags": [
          "MCP"
        ],
        "summary": "Get consent request",
        "description": "Get the client name and redirect URI for a pending consent request, so the consent page can show who is asking.",
        "security": [],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            },
            "required": true,
            "name": "requestId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Authorization request details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpAuthorizationRequest"
                }
              }
            }
          },
          "400": {
            "description": "Invalid OAuth client",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "404": {
            "description": "Unknown or expired authorization request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "decideMcpAuthorizationRequest",
        "tags": [
          "MCP"
        ],
        "summary": "Decide consent request",
        "description": "Approve or deny a pending consent request and get the URL to send the browser back to.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            },
            "required": true,
            "name": "requestId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "approved": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "approved"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OAuth client redirect",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpAuthorizationDecision"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or OAuth client",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "403": {
            "description": "Untrusted request origin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "404": {
            "description": "Unknown or expired authorization request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/oauth/id-token": {
      "get": {
        "operationId": "getOAuthIdToken",
        "tags": [
          "Authentication"
        ],
        "summary": "Get OAuth id token",
        "description": "Get the id_token for the current user's custom OAuth account. Returns null when the user signed in another way.",
        "responses": {
          "200": {
            "description": "The id_token if available",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthIdToken"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/project/{id}/move": {
      "put": {
        "operationId": "moveProject",
        "tags": [
          "Projects"
        ],
        "summary": "Move a project to another workspace",
        "description": "Move a project and its tasks. Requires update and delete permission in the source, plus project creation and workspace settings management permission in the destination. Remove cross-project task relationships before moving.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspaceId": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "workspaceId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Project moved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MovedProject"
                }
              }
            }
          },
          "400": {
            "description": "Invalid destination or same workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "402": {
            "description": "Destination workspace plan has expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing permission in the source or destination workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or destination workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Project key conflict or cross-project task relationships",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/project": {
      "get": {
        "operationId": "listProjects",
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "description": "List a workspace's projects in sidebar order, each with rollup task statistics. Archived projects are excluded unless includeArchived is set.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Pass \"true\" to include archived projects in the list."
            },
            "required": false,
            "description": "Pass \"true\" to include archived projects in the list.",
            "name": "includeArchived",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "List of projects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectListItem"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Workspace ID could not be determined",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createProject",
        "tags": [
          "Projects"
        ],
        "summary": "Create project",
        "description": "Create a project in a workspace. The slug becomes the prefix of its task identifiers.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "workspaceId": {
                    "type": "string"
                  },
                  "icon": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string"
                  }
                },
                "required": [
                  "name",
                  "workspaceId",
                  "icon",
                  "slug"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, or workspace ID could not be determined",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing project:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Another project in the workspace uses this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/project/{id}": {
      "get": {
        "operationId": "getProject",
        "tags": [
          "Projects"
        ],
        "summary": "Get project",
        "description": "Get a single project by ID.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Project details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "put": {
        "operationId": "updateProject",
        "tags": [
          "Projects"
        ],
        "summary": "Update project",
        "description": "Replace a project's name, icon, slug, description, and visibility.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "icon": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "isPublic": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name",
                  "icon",
                  "slug",
                  "description",
                  "isPublic"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, missing project:update, or missing project:share when visibility changes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Another project in the workspace uses the new key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteProject",
        "tags": [
          "Projects"
        ],
        "summary": "Delete project",
        "description": "Permanently delete a project and everything in it. Archive it instead to keep the data.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:delete permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/project/{id}/background": {
      "get": {
        "operationId": "getProjectBackground",
        "tags": [
          "Projects"
        ],
        "summary": "Download project background",
        "description": "Download the current project board background image.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The project background image",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or project background not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteProjectBackground",
        "tags": [
          "Projects"
        ],
        "summary": "Delete project background",
        "description": "Remove the current project board background image.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Project background removed"
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/project/reorder": {
      "put": {
        "operationId": "reorderProjects",
        "tags": [
          "Projects"
        ],
        "summary": "Reorder projects",
        "description": "Set the sidebar order of a workspace's projects. The given positions express relative order only -- the workspace is renumbered to 0..n-1.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "query"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projects": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "position": {
                          "type": "integer",
                          "minimum": 0
                        }
                      },
                      "required": [
                        "id",
                        "position"
                      ]
                    },
                    "minItems": 1
                  }
                },
                "required": [
                  "projects"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The reordered projects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Project"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, or workspace ID could not be determined",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing project:update permission, or no access to a listed project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/project/{id}/archive": {
      "put": {
        "operationId": "archiveProject",
        "tags": [
          "Projects"
        ],
        "summary": "Archive project",
        "description": "Hide a project from the default list without deleting it. Reversible with unarchive.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The archived project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/project/{id}/unarchive": {
      "put": {
        "operationId": "unarchiveProject",
        "tags": [
          "Projects"
        ],
        "summary": "Unarchive project",
        "description": "Return an archived project to the default list.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The restored project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Another project in the workspace uses this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/project/{id}/background-upload": {
      "put": {
        "operationId": "uploadProjectBackground",
        "tags": [
          "Projects"
        ],
        "summary": "Prepare project background upload",
        "description": "Create a presigned background image upload URL for a project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contentType": {
                    "type": "string"
                  },
                  "size": {
                    "type": "number"
                  }
                },
                "required": [
                  "contentType",
                  "size"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Background image upload URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectBackgroundUpload"
                }
              }
            }
          },
          "400": {
            "description": "Invalid image upload request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "503": {
            "description": "Image uploads are not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/project/{id}/background-upload/finalize": {
      "post": {
        "operationId": "finalizeProjectBackgroundUpload",
        "tags": [
          "Projects"
        ],
        "summary": "Finalize project background upload",
        "description": "Save an uploaded image as the project's board background.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "key": {
                    "type": "string"
                  },
                  "contentType": {
                    "type": "string"
                  },
                  "version": {
                    "type": "string"
                  },
                  "size": {
                    "type": "number"
                  }
                },
                "required": [
                  "key",
                  "contentType",
                  "version",
                  "size"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Finalized project background",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectBackgroundFinalize"
                }
              }
            }
          },
          "400": {
            "description": "Invalid image upload request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "500": {
            "description": "Failed to save the project background",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/calendar-feed/project/{projectId}": {
      "get": {
        "operationId": "listCalendarFeeds",
        "tags": [
          "Calendar feeds"
        ],
        "summary": "List project calendar feeds",
        "description": "List secret calendar subscription links. Requires project sharing permission.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Calendar feeds",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CalendarFeed"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project sharing permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createCalendarFeed",
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Create a calendar feed",
        "description": "Create a calendar subscription matching any selected label, or all project tasks when no labels are selected. Tasks need a start or due date. All-day dates use the supplied time zone. Creating a missing workspace label definition also requires label:create permission.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "labelIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    },
                    "maxItems": 100,
                    "default": [],
                    "description": "Match any selected label. Omit or leave empty to include all scheduled project tasks."
                  },
                  "timeZone": {
                    "type": "string",
                    "default": "UTC"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Calendar feed created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarFeed"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, missing project:share permission, or missing label:create permission for a new workspace label definition",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/calendar-feed/project/{projectId}/{id}": {
      "delete": {
        "operationId": "revokeCalendarFeed",
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Revoke a calendar feed",
        "description": "Revoke a calendar subscription link, preventing further access through it.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Calendar feed revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project sharing permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or calendar feed not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/{id}/description": {
      "get": {
        "operationId": "getTaskDescriptionPage",
        "tags": [
          "Tasks"
        ],
        "summary": "Read a description page",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 2000000000,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,10}$"
            },
            "required": false,
            "name": "version",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Description page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskDescriptionPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid offset or version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Description changed; reload from offset zero",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "503": {
            "description": "Description request timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/task/description-matches/{projectId}": {
      "get": {
        "operationId": "findDeferredTaskDescriptions",
        "tags": [
          "Tasks"
        ],
        "summary": "Search descriptions omitted from task lists",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            },
            "required": true,
            "name": "query",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            },
            "required": false,
            "name": "after",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching task IDs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskDescriptionMatches"
                }
              }
            }
          },
          "400": {
            "description": "Invalid search query or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "503": {
            "description": "Description search timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/task/assigned": {
      "get": {
        "operationId": "listAssignedTasks",
        "tags": [
          "Tasks"
        ],
        "summary": "List my assigned tasks",
        "description": "Get the open tasks assigned to the caller across a workspace's active projects. Completed and archived tasks are excluded. Ordered by due date (undated last), then priority. At most 100 tasks are returned; total counts all of them. Set countOnly=true to return just that total without loading task rows or labels.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "workspaceId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Return only the total, with an empty tasks array, without loading task rows or labels."
            },
            "required": false,
            "description": "Return only the total, with an empty tasks array, without loading task rows or labels.",
            "name": "countOnly",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Open tasks assigned to the caller",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssignedTasks"
                }
              }
            }
          },
          "400": {
            "description": "Invalid workspace ID or query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing project:read or task:read permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/tasks/{projectId}": {
      "get": {
        "operationId": "listTasks",
        "tags": [
          "Tasks"
        ],
        "summary": "List tasks",
        "description": "Get a project's board: its columns, each with the tasks in it, plus the archived and planned buckets. Responses always contain at most 100 tasks (50 by default). Continue through pagination.totalPages for the whole board, and for each task page follow relatedPage through pagination.relatedTotalPages for all labels, links and column metadata. Filters and sorting apply before pagination. Descriptions larger than 64 KiB are omitted with descriptionDeferred=true; read the task detail or description pages for full text.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "priority",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "assigneeId",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000
            },
            "required": false,
            "name": "relatedPage",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "createdAt",
                "priority",
                "dueDate",
                "position",
                "title",
                "number"
              ]
            },
            "required": false,
            "name": "sortBy",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "required": false,
            "name": "sortOrder",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "dueBefore",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "dueAfter",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The project board",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BoardResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "503": {
            "description": "Task list request timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/task/bulk": {
      "patch": {
        "operationId": "bulkUpdateTasks",
        "tags": [
          "Tasks"
        ],
        "summary": "Bulk update tasks",
        "description": "Apply one operation to many tasks at once. Every task must be in the same workspace.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "taskIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1
                  },
                  "operation": {
                    "type": "string",
                    "enum": [
                      "updateStatus",
                      "updatePriority",
                      "updateAssignee",
                      "delete",
                      "addLabel",
                      "removeLabel",
                      "updateDueDate"
                    ]
                  },
                  "value": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The new value for the chosen operation. Unused by `delete`; null clears an assignee or due date."
                  }
                },
                "required": [
                  "taskIds",
                  "operation"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bulk operation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkTaskResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, or the tasks span more than one workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing the permission the operation needs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "No tasks found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Tasks changed projects; retry the operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/draft-upload/{projectId}": {
      "post": {
        "operationId": "stageTaskAssetUpload",
        "tags": [
          "Tasks"
        ],
        "summary": "Stage a task attachment",
        "description": "Upload an attachment before submitting a task. Staged assets are private to the uploader and expire after 24 hours.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string"
                  },
                  "contentType": {
                    "type": "string"
                  },
                  "size": {
                    "type": "number"
                  },
                  "surface": {
                    "type": "string",
                    "enum": [
                      "description"
                    ]
                  }
                },
                "required": [
                  "filename",
                  "contentType",
                  "size",
                  "surface"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Presigned upload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskImageUpload"
                }
              }
            }
          },
          "400": {
            "description": "Invalid upload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing task:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "503": {
            "description": "Storage unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/task/draft-upload/{projectId}/finalize": {
      "post": {
        "operationId": "finalizeStagedTaskAsset",
        "tags": [
          "Tasks"
        ],
        "summary": "Finalize a staged task attachment",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "key": {
                    "type": "string",
                    "description": "The key returned when the URL was issued."
                  },
                  "filename": {
                    "type": "string"
                  },
                  "contentType": {
                    "type": "string"
                  },
                  "size": {
                    "type": "number"
                  },
                  "surface": {
                    "type": "string",
                    "enum": [
                      "description"
                    ]
                  }
                },
                "required": [
                  "key",
                  "filename",
                  "contentType",
                  "size",
                  "surface"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Staged attachment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskImageAsset"
                }
              }
            }
          },
          "400": {
            "description": "Invalid upload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing task:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Upload already attached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "503": {
            "description": "Storage unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/task/reorder": {
      "post": {
        "operationId": "reorderTasks",
        "tags": [
          "Tasks"
        ],
        "summary": "Reorder or move cards",
        "description": "Atomically update positions and optional column status for cards in one project. Other task fields are preserved.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectId": {
                    "type": "string"
                  },
                  "expectedTasks": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "position": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 2147483646
                        },
                        "status": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "position",
                        "status"
                      ]
                    },
                    "description": "Previous complete contents of the affected columns; stale snapshots return 409"
                  },
                  "tasks": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "position": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 2147483646
                        },
                        "status": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "position"
                      ]
                    },
                    "minItems": 1
                  }
                },
                "required": [
                  "projectId",
                  "tasks"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated card positions and statuses",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "position": {
                        "type": "number"
                      },
                      "status": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "id",
                      "position",
                      "status"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid positions or column",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found, or tasks do not belong to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Board changed or task moved; refresh before reordering",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/{projectId}": {
      "post": {
        "operationId": "createTask",
        "tags": [
          "Tasks"
        ],
        "summary": "Create task",
        "description": "Add a task to a project. It is placed in the column named by `status`.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "startDate": {
                    "type": "string"
                  },
                  "dueDate": {
                    "type": "string"
                  },
                  "priority": {
                    "type": "string",
                    "enum": [
                      "no-priority",
                      "low",
                      "medium",
                      "high",
                      "urgent"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "description": "The target column's slug."
                  },
                  "userId": {
                    "type": "string",
                    "description": "Assignee, if any."
                  },
                  "draftAssetIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 100
                  },
                  "customFields": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "fieldId": {
                          "type": "string"
                        },
                        "value": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "fieldId",
                        "value"
                      ]
                    }
                  }
                },
                "required": [
                  "title",
                  "description",
                  "priority",
                  "status"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/duplicate/{id}": {
      "post": {
        "operationId": "duplicateTask",
        "tags": [
          "Tasks"
        ],
        "summary": "Duplicate a task",
        "description": "Copy a task into the same project and column, including custom fields, labels, description assets and same-workspace parent links. Copying parent links also requires task:update. Comments, time entries and child tasks are not copied.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The duplicated task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Invalid task fields or request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, missing task:create, or missing task:update when copying parent links",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task or project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Task column has reached its capacity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "503": {
            "description": "Unable to copy task attachments",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/task/by-ticket-id/{ticketId}": {
      "get": {
        "operationId": "getTaskByTicketId",
        "tags": [
          "Tasks"
        ],
        "summary": "Get task by ticket ID",
        "description": "Get a single task by its project key and number, such as KAN-12. A match in an active project takes precedence, then the most recently archived project. If the ticket ID still matches multiple accessible tasks, narrow the lookup to a workspace with workspaceId or workspaceSlug, or to a single project with projectId.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 128,
              "description": "Project key and task number, e.g. KAN-12."
            },
            "required": true,
            "description": "Project key and task number, e.g. KAN-12.",
            "name": "ticketId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Select a workspace if the ticket ID exists in more than one."
            },
            "required": false,
            "description": "Select a workspace if the ticket ID exists in more than one.",
            "name": "workspaceId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "description": "Select a workspace by its slug instead of its ID."
            },
            "required": false,
            "description": "Select a workspace by its slug instead of its ID.",
            "name": "workspaceSlug",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Select a project if the ticket ID exists more than once."
            },
            "required": false,
            "description": "Select a project if the ticket ID exists more than once.",
            "name": "projectId",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Task details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskByTicketId"
                }
              }
            }
          },
          "400": {
            "description": "Invalid ticket ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "No accessible task has this ticket ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Ticket ID matches multiple accessible tasks",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/{id}": {
      "get": {
        "operationId": "getTask",
        "tags": [
          "Tasks"
        ],
        "summary": "Get task",
        "description": "Get a single task by ID, with its assignee name. The board view omits descriptions above 64 KiB and includes task and same-project parent subtask progress.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "detail",
                "board"
              ]
            },
            "required": false,
            "name": "view",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Task details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskWithAssignee"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "put": {
        "operationId": "updateTask",
        "tags": [
          "Tasks"
        ],
        "summary": "Update task",
        "description": "Replace every field of a task. Use the single-field routes for narrower edits.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string",
                    "description": "Omit to preserve the existing description when updating a list summary."
                  },
                  "startDate": {
                    "type": "string"
                  },
                  "dueDate": {
                    "type": "string"
                  },
                  "priority": {
                    "type": "string",
                    "enum": [
                      "no-priority",
                      "low",
                      "medium",
                      "high",
                      "urgent"
                    ]
                  },
                  "status": {
                    "type": "string"
                  },
                  "projectId": {
                    "type": "string"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 2147483646
                  },
                  "userId": {
                    "type": "string"
                  }
                },
                "required": [
                  "title",
                  "priority",
                  "status",
                  "projectId",
                  "position"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update or task:assign permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteTask",
        "tags": [
          "Tasks"
        ],
        "summary": "Delete task",
        "description": "Permanently delete a task and its comments, labels, and time entries.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:delete permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Task changed projects; retry the operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/move/{id}": {
      "put": {
        "operationId": "moveTask",
        "tags": [
          "Tasks"
        ],
        "summary": "Move task",
        "description": "Move a task to another project, optionally into a named column. Both projects must be in the same workspace.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "destinationProjectId": {
                    "type": "string"
                  },
                  "destinationStatus": {
                    "type": "string",
                    "description": "Defaults to the destination project's first column."
                  }
                },
                "required": [
                  "destinationProjectId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The moved task, with both project ids",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoveTaskResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task or destination project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Task or project moved concurrently; retry the move",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/export/{projectId}": {
      "get": {
        "operationId": "exportTasks",
        "tags": [
          "Tasks"
        ],
        "summary": "Export tasks",
        "description": "Export a project's tasks, with their labels, as a JSON document.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The exported project and tasks",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskExport"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/import/{projectId}": {
      "post": {
        "operationId": "importTasks",
        "tags": [
          "Tasks"
        ],
        "summary": "Import tasks",
        "description": "Import tasks into a project. Each task is reported individually, so a partial import still returns 200.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tasks": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "description": "Column slug, current column name, or slugified current name. Unknown values map to planned with a warning; ambiguous names fail the task import."
                        },
                        "priority": {
                          "type": "string"
                        },
                        "startDate": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dueDate": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "userId": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Assignable user's ID or email address. Emails match case-insensitively. Null, empty, or omitted leaves the task unassigned."
                        }
                      },
                      "required": [
                        "title",
                        "status"
                      ]
                    }
                  }
                },
                "required": [
                  "tasks"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-task import outcome",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskImportResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/status/{id}": {
      "put": {
        "operationId": "updateTaskStatus",
        "tags": [
          "Tasks"
        ],
        "summary": "Update task status",
        "description": "Move a task to another column in the same project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string"
                  }
                },
                "required": [
                  "status"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/priority/{id}": {
      "put": {
        "operationId": "updateTaskPriority",
        "tags": [
          "Tasks"
        ],
        "summary": "Update task priority",
        "description": "Set a task's priority.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "priority": {
                    "type": "string",
                    "enum": [
                      "no-priority",
                      "low",
                      "medium",
                      "high",
                      "urgent"
                    ]
                  }
                },
                "required": [
                  "priority"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Invalid priority",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/assignee/{id}": {
      "put": {
        "operationId": "updateTaskAssignee",
        "tags": [
          "Tasks"
        ],
        "summary": "Update task assignee",
        "description": "Assign a task to a workspace member, or send null to unassign it.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "userId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Null unassigns."
                  }
                },
                "required": [
                  "userId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:assign permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found, or assignee is not a member of the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/due-date/{id}": {
      "put": {
        "operationId": "updateTaskDueDate",
        "tags": [
          "Tasks"
        ],
        "summary": "Update task due date",
        "description": "Set or clear a task's due date.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "dueDate": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Invalid date",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/title/{id}": {
      "put": {
        "operationId": "updateTaskTitle",
        "tags": [
          "Tasks"
        ],
        "summary": "Update task title",
        "description": "Rename a task.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  }
                },
                "required": [
                  "title"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/image-upload/{id}": {
      "put": {
        "operationId": "createTaskImageUpload",
        "tags": [
          "Tasks"
        ],
        "summary": "Create image upload URL",
        "description": "Get a presigned URL for uploading an image used in a task description or comment. PUT the bytes to it, then call the finalize route.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string"
                  },
                  "contentType": {
                    "type": "string"
                  },
                  "size": {
                    "type": "number"
                  },
                  "surface": {
                    "type": "string",
                    "enum": [
                      "description",
                      "comment"
                    ],
                    "description": "Where the image is used, which decides how it is scoped."
                  }
                },
                "required": [
                  "filename",
                  "contentType",
                  "size",
                  "surface"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The presigned upload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskImageUpload"
                }
              }
            }
          },
          "400": {
            "description": "Unsupported content type, or the file is too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "503": {
            "description": "Image uploads are not configured on this instance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/task/image-upload/{id}/finalize": {
      "post": {
        "operationId": "finalizeTaskImageUpload",
        "tags": [
          "Tasks"
        ],
        "summary": "Finalize image upload",
        "description": "Record an uploaded image as a private asset and return the URL to reference it by.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "key": {
                    "type": "string",
                    "description": "The key returned when the URL was issued."
                  },
                  "filename": {
                    "type": "string"
                  },
                  "contentType": {
                    "type": "string"
                  },
                  "size": {
                    "type": "number"
                  },
                  "surface": {
                    "type": "string",
                    "enum": [
                      "description",
                      "comment"
                    ],
                    "description": "Where the image is used, which decides how it is scoped."
                  }
                },
                "required": [
                  "key",
                  "filename",
                  "contentType",
                  "size",
                  "surface"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored asset",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskImageAsset"
                }
              }
            }
          },
          "400": {
            "description": "Invalid upload, or the key does not belong to this task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task/description/{id}": {
      "put": {
        "operationId": "updateTaskDescription",
        "tags": [
          "Tasks"
        ],
        "summary": "Update task description",
        "description": "Replace a task's description.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "description": {
                    "type": "string"
                  }
                },
                "required": [
                  "description"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/column/{projectId}": {
      "get": {
        "operationId": "getColumns",
        "tags": [
          "Columns"
        ],
        "summary": "Get columns",
        "description": "Get a project's board columns, ordered by position.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "List of columns ordered by position",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Column"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createColumn",
        "tags": [
          "Columns"
        ],
        "summary": "Create column",
        "description": "Add a column to the end of a project's board. The slug is derived from the name, with a numeric suffix such as `-2` when another column in the project already uses it.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "icon": {
                    "type": "string"
                  },
                  "color": {
                    "type": "string"
                  },
                  "isFinal": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created column",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Column"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The slug is reserved for a virtual task status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/column/reorder/{projectId}": {
      "put": {
        "operationId": "reorderColumns",
        "tags": [
          "Columns"
        ],
        "summary": "Reorder columns",
        "description": "Set new positions for a project's columns and return the whole board in its new order.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "columns": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "position": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "id",
                        "position"
                      ]
                    },
                    "description": "Every column keeps its new position. Columns from another project are rejected."
                  }
                },
                "required": [
                  "columns"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The reordered columns",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Column"
                  }
                }
              }
            }
          },
          "400": {
            "description": "A column does not belong to this project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/column/{id}": {
      "put": {
        "operationId": "updateColumn",
        "tags": [
          "Columns"
        ],
        "summary": "Update column",
        "description": "Update a column. Omitted fields are left unchanged; icon and color accept null to clear them.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "icon": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "isFinal": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated column",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Column"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Column not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteColumn",
        "tags": [
          "Columns"
        ],
        "summary": "Delete column",
        "description": "Delete an empty column. A column holding tasks is refused until they are moved or deleted.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted column",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Column"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Column not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The column still contains tasks",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/activity/workspace/{workspaceId}": {
      "get": {
        "operationId": "getWorkspaceActivities",
        "tags": [
          "Activity"
        ],
        "summary": "Get recent workspace activity",
        "description": "Get the 20 most recent task events across a workspace's active projects from the last 30 days, newest first. Each event carries a short plain-text excerpt instead of its full content.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Recent activity in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WorkspaceActivity"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing project:read or task:read permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/activity/{taskId}": {
      "get": {
        "operationId": "getActivities",
        "tags": [
          "Activity"
        ],
        "summary": "Get task activity",
        "description": "Get a task's activity feed, newest first: comments alongside system events such as status and assignee changes. Set limit to request a bounded preview; omit it for the full feed.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "description": "Maximum number of recent activities; omit for the full feed."
            },
            "required": false,
            "description": "Maximum number of recent activities; omit for the full feed.",
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "List of activities for the task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Activity"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/activity/create": {
      "post": {
        "operationId": "createActivity",
        "tags": [
          "Activity"
        ],
        "summary": "Create activity",
        "description": "Record a system-generated event on a task. Most events are written by the server itself; this exists for importers and integrations.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "taskId": {
                    "type": "string"
                  },
                  "message": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Free-text body. Null for events whose meaning is in eventData."
                  },
                  "type": {
                    "type": "string",
                    "description": "The event kind, e.g. status_changed or assignee_changed."
                  },
                  "eventData": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": {},
                    "description": "Type-specific payload stored alongside the event."
                  }
                },
                "required": [
                  "taskId",
                  "message",
                  "type"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created activity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Activity"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, or comment activity submitted through the generic endpoint",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/activity/comment": {
      "post": {
        "operationId": "createComment",
        "tags": [
          "Activity"
        ],
        "summary": "Create comment",
        "description": "Add a comment to a task. Equivalent to POST /comment/{taskId}, kept for the activity-feed client.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "taskId": {
                    "type": "string"
                  },
                  "comment": {
                    "type": "string",
                    "maxLength": 10000
                  }
                },
                "required": [
                  "taskId",
                  "comment"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created comment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Activity"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "put": {
        "operationId": "updateComment",
        "tags": [
          "Activity"
        ],
        "summary": "Update comment",
        "description": "Edit a comment. Only the comment's author may do this.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "activityId": {
                    "type": "string"
                  },
                  "comment": {
                    "type": "string",
                    "maxLength": 10000
                  }
                },
                "required": [
                  "activityId",
                  "comment"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated comment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Activity"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Not the author, or no access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Activity or comment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteComment",
        "tags": [
          "Activity"
        ],
        "summary": "Delete comment",
        "description": "Delete a comment. Only the comment's author may do this.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "activityId": {
                    "type": "string"
                  }
                },
                "required": [
                  "activityId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The deleted comment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Activity"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Not the author, or no access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Activity or comment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/comment/{taskId}": {
      "get": {
        "operationId": "getTaskComments",
        "tags": [
          "Comments"
        ],
        "summary": "Get task comments",
        "description": "Get every comment on a task, oldest first, each with its author's name and avatar.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "List of comments for the task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Comment"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createTaskComment",
        "tags": [
          "Comments"
        ],
        "summary": "Create task comment",
        "description": "Add a comment to a task. Mentions in the content notify the mentioned users, and the assignee is notified too. Returns the stored activity row, which carries no author object -- read the list route for that.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  },
                  "externalUserName": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Attribution for an imported comment. Requires workspace:manage_settings and externalSource; otherwise ignored."
                  },
                  "externalSource": {
                    "type": "string",
                    "enum": [
                      "planka",
                      "trello",
                      "jira",
                      "plane",
                      "linear",
                      "kaneo"
                    ],
                    "description": "The tool the comment was imported from. Supplying external attribution requires workspace:manage_settings."
                  }
                },
                "required": [
                  "content"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created comment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Activity"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, missing task:update permission, or external attribution without workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/comment/{id}": {
      "put": {
        "operationId": "updateTaskComment",
        "tags": [
          "Comments"
        ],
        "summary": "Update task comment",
        "description": "Edit a comment. Only the comment's author may do this.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  }
                },
                "required": [
                  "content"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated comment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Activity"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Not the author, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Comment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteTaskComment",
        "tags": [
          "Comments"
        ],
        "summary": "Delete task comment",
        "description": "Delete a comment. Only the comment's author may do this.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted comment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Activity"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Not the author, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Comment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/time-entry/task/{taskId}": {
      "get": {
        "operationId": "getTaskTimeEntries",
        "tags": [
          "Time Entries"
        ],
        "summary": "Get task time entries",
        "description": "Get every time entry logged against a task.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "List of time entries for the task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TimeEntryWithUser"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/time-entry/{id}": {
      "get": {
        "operationId": "getTimeEntry",
        "tags": [
          "Time Entries"
        ],
        "summary": "Get time entry",
        "description": "Get a single time entry by ID.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Time entry details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimeEntry"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Time entry not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "put": {
        "operationId": "updateTimeEntry",
        "tags": [
          "Time Entries"
        ],
        "summary": "Update time entry",
        "description": "Replace a time entry's start, end, and description. Setting endTime closes a running entry and fills in its duration.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "startTime": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-01-31T09:00:00Z"
                  },
                  "endTime": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-01-31T09:00:00Z"
                  },
                  "description": {
                    "type": "string"
                  }
                },
                "required": [
                  "startTime"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated time entry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimeEntry"
                }
              }
            }
          },
          "400": {
            "description": "Invalid timestamps",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Time entry not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/time-entry": {
      "post": {
        "operationId": "createTimeEntry",
        "tags": [
          "Time Entries"
        ],
        "summary": "Create time entry",
        "description": "Log time against a task. Omit endTime to start a running entry that can be closed later with an update.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "taskId": {
                    "type": "string"
                  },
                  "startTime": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-01-31T09:00:00Z"
                  },
                  "endTime": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-01-31T09:00:00Z",
                    "description": "Omit to start an open-ended entry that is still running."
                  },
                  "description": {
                    "type": "string"
                  }
                },
                "required": [
                  "taskId",
                  "startTime"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created time entry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimeEntry"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or timestamps",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/label/task/{taskId}": {
      "get": {
        "operationId": "getTaskLabels",
        "tags": [
          "Labels"
        ],
        "summary": "Get task labels",
        "description": "Get all labels assigned to a specific task",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "List of labels for the task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Label"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/label/workspace/{workspaceId}": {
      "get": {
        "operationId": "getWorkspaceLabels",
        "tags": [
          "Labels"
        ],
        "summary": "Get workspace labels",
        "description": "Get all labels for a specific workspace",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "List of labels in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Label"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/label": {
      "post": {
        "operationId": "createLabel",
        "tags": [
          "Labels"
        ],
        "summary": "Create label",
        "description": "Create a new label in a workspace",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "color": {
                    "type": "string"
                  },
                  "workspaceId": {
                    "type": "string"
                  },
                  "taskId": {
                    "type": "string"
                  }
                },
                "required": [
                  "name",
                  "color",
                  "workspaceId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Label created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, or workspace ID could not be determined",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing label:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace or task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The workspace label is being deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/label/{id}": {
      "get": {
        "operationId": "getLabel",
        "tags": [
          "Labels"
        ],
        "summary": "Get label",
        "description": "Get a specific label by ID",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Label details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Label not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "put": {
        "operationId": "updateLabel",
        "tags": [
          "Labels"
        ],
        "summary": "Update label",
        "description": "Update an existing label",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "color": {
                    "type": "string"
                  }
                },
                "required": [
                  "name",
                  "color"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Label updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing label:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Label not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The workspace label is being deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteLabel",
        "tags": [
          "Labels"
        ],
        "summary": "Delete label",
        "description": "Delete a label by ID. Large workspace cascades remove at most 25 task copies per request. Repeat DELETE with the same ID after HTTP 202 until HTTP 200 completes the operation. The persisted start boundary allows safe resumption after a disconnect. HTTP 429 asks the caller to retry later.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Label deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          },
          "202": {
            "description": "Deletion has more batches; repeat the same request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingLabelDeletion"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing label:delete permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Label not found, or the label's task no longer exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Deletion capacity is busy; retry after Retry-After",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/label/{id}/task": {
      "put": {
        "operationId": "attachLabelToTask",
        "tags": [
          "Labels"
        ],
        "summary": "Attach label to task",
        "description": "Attach an existing label to a task",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "taskId": {
                    "type": "string"
                  }
                },
                "required": [
                  "taskId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Label attached to task successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          },
          "400": {
            "description": "Label and task belong to different workspaces",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing label:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Label or task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The workspace label is being deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "detachLabelFromTask",
        "tags": [
          "Labels"
        ],
        "summary": "Detach label from task",
        "description": "Detach a label from its current task",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Label detached from task successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          },
          "400": {
            "description": "Label is not assigned to a task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing label:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Label or task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/notification": {
      "get": {
        "operationId": "listNotifications",
        "tags": [
          "Notifications"
        ],
        "summary": "List notifications",
        "description": "Get up to 50 notifications for the current user, read and unread. Optionally limit them to one workspace before applying the limit.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Limit notifications to resources currently in this workspace, plus user-wide notifications with no resource. Omit to include all workspaces."
            },
            "required": false,
            "description": "Limit notifications to resources currently in this workspace, plus user-wide notifications with no resource. Omit to include all workspaces.",
            "name": "workspaceId",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "List of notifications",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Notification"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createNotification",
        "tags": [
          "Notifications"
        ],
        "summary": "Create notification",
        "description": "Create a notification for the current user. Most notifications are raised by the server from task and workspace events; this exists for integrations. Returns null when the user has turned off this notification category in their preferences.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "message": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Stored as the notification's content."
                  },
                  "type": {
                    "type": "string"
                  },
                  "eventData": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": {}
                  },
                  "relatedEntityId": {
                    "type": "string",
                    "description": "Stored as resourceId: the task or workspace being pointed at."
                  },
                  "relatedEntityType": {
                    "type": "string",
                    "description": "Stored as resourceType: `task` or `workspace`."
                  }
                },
                "required": [
                  "type"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created notification, or null when the user has muted this notification type",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Notification"
                    },
                    {
                      "type": [
                        "object",
                        "null"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/notification/{id}/read": {
      "patch": {
        "operationId": "markNotificationAsRead",
        "tags": [
          "Notifications"
        ],
        "summary": "Mark notification read",
        "description": "Mark one notification as read. Scoped to the current user, so another user's notification is not found.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The updated notification",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Notification"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Notification not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/notification/{id}/unread": {
      "patch": {
        "operationId": "markNotificationAsUnread",
        "tags": [
          "Notifications"
        ],
        "summary": "Mark notification unread",
        "description": "Mark one notification as unread again. Scoped to the current user, so another user's notification is not found.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The updated notification",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Notification"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Notification not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/notification/read-all": {
      "patch": {
        "operationId": "markAllNotificationsAsRead",
        "tags": [
          "Notifications"
        ],
        "summary": "Mark all read",
        "description": "Mark notifications for the current user as read, optionally limited to one workspace.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Limit notifications to resources currently in this workspace, plus user-wide notifications with no resource. Omit to include all workspaces."
            },
            "required": false,
            "description": "Limit notifications to resources currently in this workspace, plus user-wide notifications with no resource. Omit to include all workspaces.",
            "name": "workspaceId",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "All notifications marked as read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationBulkResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/notification/clear-all": {
      "delete": {
        "operationId": "clearAllNotifications",
        "tags": [
          "Notifications"
        ],
        "summary": "Clear all",
        "description": "Permanently delete notifications for the current user, optionally limited to one workspace. This cannot be undone.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Limit notifications to resources currently in this workspace, plus user-wide notifications with no resource. Omit to include all workspaces."
            },
            "required": false,
            "description": "Limit notifications to resources currently in this workspace, plus user-wide notifications with no resource. Omit to include all workspaces.",
            "name": "workspaceId",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "All notifications cleared",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationBulkResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/notification/clear-read": {
      "delete": {
        "operationId": "clearReadNotifications",
        "tags": [
          "Notifications"
        ],
        "summary": "Clear read notifications",
        "description": "Permanently delete the current user's read notifications, optionally limited to one workspace. Unread notifications are kept. This cannot be undone.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Limit notifications to resources currently in this workspace, plus user-wide notifications with no resource. Omit to include all workspaces."
            },
            "required": false,
            "description": "Limit notifications to resources currently in this workspace, plus user-wide notifications with no resource. Omit to include all workspaces.",
            "name": "workspaceId",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Read notifications cleared",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationBulkResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/notification/{id}": {
      "delete": {
        "operationId": "deleteNotification",
        "tags": [
          "Notifications"
        ],
        "summary": "Delete notification",
        "description": "Permanently delete one notification. Scoped to the current user, so another user's notification is not found.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Notification deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationBulkResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Notification not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/notification-preferences": {
      "get": {
        "operationId": "getNotificationPreferences",
        "tags": [
          "Notification Preferences"
        ],
        "summary": "Get notification preferences",
        "description": "Get how the current user is notified, globally and per workspace. Configured secrets are reported as booleans plus a masked preview, never in full.",
        "responses": {
          "200": {
            "description": "Notification preferences",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationPreferences"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "put": {
        "operationId": "updateNotificationPreferences",
        "tags": [
          "Notification Preferences"
        ],
        "summary": "Update notification preferences",
        "description": "Update the global delivery settings. Omitted fields are left unchanged; a secret sent as null is cleared.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "emailEnabled": {
                    "type": "boolean"
                  },
                  "ntfyEnabled": {
                    "type": "boolean"
                  },
                  "ntfyServerUrl": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "ntfyTopic": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "ntfyToken": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "gotifyEnabled": {
                    "type": "boolean"
                  },
                  "gotifyServerUrl": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "gotifyToken": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "webhookEnabled": {
                    "type": "boolean"
                  },
                  "webhookUrl": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "webhookSecret": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "taskAssignmentEnabled": {
                    "type": "boolean"
                  },
                  "taskCommentEnabled": {
                    "type": "boolean"
                  },
                  "taskStatusChangeEnabled": {
                    "type": "boolean"
                  },
                  "dueDateReminderEnabled": {
                    "type": "boolean"
                  },
                  "dueDateReminderLeadTimeMinutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 43200,
                    "description": "Between 5 minutes and 30 days."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated preferences",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationPreferences"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/notification-preferences/workspaces/{workspaceId}": {
      "put": {
        "operationId": "upsertNotificationPreferenceWorkspaceRule",
        "tags": [
          "Notification Preferences"
        ],
        "summary": "Upsert workspace rule",
        "description": "Create or replace the notification rule for one workspace, overriding the global settings there.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "isActive": {
                    "type": "boolean"
                  },
                  "emailEnabled": {
                    "type": "boolean"
                  },
                  "ntfyEnabled": {
                    "type": "boolean"
                  },
                  "gotifyEnabled": {
                    "type": "boolean"
                  },
                  "webhookEnabled": {
                    "type": "boolean"
                  },
                  "projectMode": {
                    "type": "string",
                    "enum": [
                      "all",
                      "selected"
                    ]
                  },
                  "selectedProjectIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Required in practice when projectMode is `selected`."
                  }
                },
                "required": [
                  "isActive",
                  "emailEnabled",
                  "ntfyEnabled",
                  "gotifyEnabled",
                  "webhookEnabled",
                  "projectMode"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated preferences",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationPreferences"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteNotificationPreferenceWorkspaceRule",
        "tags": [
          "Notification Preferences"
        ],
        "summary": "Delete workspace rule",
        "description": "Remove a workspace's rule so the workspace falls back to the global settings.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The updated preferences",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationPreferences"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace rule not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/search": {
      "get": {
        "operationId": "globalSearch",
        "tags": [
          "Search"
        ],
        "summary": "Global search",
        "description": "Search across tasks, projects, workspaces, comments, and activities in one workspace. Results are ranked by relevance and returned as a single flat list, each entry tagged with its `type`.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 512
            },
            "required": true,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "tasks",
                "projects",
                "workspaces",
                "comments",
                "activities"
              ],
              "default": "all"
            },
            "required": false,
            "name": "type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "workspaceId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "projectId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "default": "20"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "email"
            },
            "required": false,
            "name": "userEmail",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked search results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, or workspace ID could not be determined",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/integration-sync/project/{projectId}/{provider}": {
      "get": {
        "operationId": "getIntegrationSyncRules",
        "tags": [
          "Integration sync"
        ],
        "summary": "Get sync rules and current task scope",
        "description": "Workspace members with project and task read permissions can inspect saved rules. Rules remain active when advanced mode is hidden. Paused issue links are preserved; eligible paused tasks require review before resuming.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "github",
                "gitea",
                "gitlab"
              ]
            },
            "required": true,
            "name": "provider",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "after",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Saved rules and scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntegrationSyncPreview"
                }
              }
            }
          },
          "400": {
            "description": "Invalid rule or workspace label",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing required permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project, integration, or linked task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration, impact or comparison changed; review again",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "patch": {
        "operationId": "saveIntegrationSyncRules",
        "tags": [
          "Integration sync"
        ],
        "summary": "Apply a previewed sync rule",
        "description": "Save shared integration rules only if the preview is still current. Excluded links are paused, never deleted. Newly matching unlinked tasks are exported through the integration event path. Paused links require explicit review to resume.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "github",
                "gitea",
                "gitlab"
              ]
            },
            "required": true,
            "name": "provider",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rules": {
                    "type": "object",
                    "properties": {
                      "outgoing": {
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "mode": {
                                "type": "string",
                                "enum": [
                                  "all"
                                ]
                              }
                            },
                            "required": [
                              "mode"
                            ]
                          },
                          {
                            "type": "object",
                            "properties": {
                              "mode": {
                                "type": "string",
                                "enum": [
                                  "labels"
                                ]
                              },
                              "match": {
                                "type": "string",
                                "enum": [
                                  "any",
                                  "all"
                                ]
                              },
                              "labels": {
                                "type": "array",
                                "items": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 128
                                },
                                "minItems": 1,
                                "maxItems": 50
                              }
                            },
                            "required": [
                              "mode",
                              "match",
                              "labels"
                            ]
                          }
                        ],
                        "description": "Kaneo workspace label IDs controlling task sync. Missing labels pause sync."
                      },
                      "incoming": {
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "mode": {
                                "type": "string",
                                "enum": [
                                  "all"
                                ]
                              }
                            },
                            "required": [
                              "mode"
                            ]
                          },
                          {
                            "type": "object",
                            "properties": {
                              "mode": {
                                "type": "string",
                                "enum": [
                                  "labels"
                                ]
                              },
                              "match": {
                                "type": "string",
                                "enum": [
                                  "any",
                                  "all"
                                ]
                              },
                              "labels": {
                                "type": "array",
                                "items": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 128
                                },
                                "minItems": 1,
                                "maxItems": 50
                              }
                            },
                            "required": [
                              "mode",
                              "match",
                              "labels"
                            ]
                          }
                        ],
                        "description": "Exact repository label names controlling which issues can be imported."
                      }
                    },
                    "required": [
                      "outgoing",
                      "incoming"
                    ]
                  },
                  "previewToken": {
                    "type": "string",
                    "minLength": 64,
                    "maxLength": 64
                  }
                },
                "required": [
                  "rules",
                  "previewToken"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved rules and current scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntegrationSyncPreview"
                }
              }
            }
          },
          "400": {
            "description": "Invalid rule or workspace label",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing required permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project, integration, or linked task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration, impact or comparison changed; review again",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/integration-sync/project/{projectId}/{provider}/preview": {
      "post": {
        "operationId": "previewIntegrationSyncRules",
        "tags": [
          "Integration sync"
        ],
        "summary": "Preview a sync rule change",
        "description": "Returns matching tasks, new exports, paused links and a token bound to the current integration and impact. Outgoing labels are workspace label IDs; incoming labels are exact repository label names.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "github",
                "gitea",
                "gitlab"
              ]
            },
            "required": true,
            "name": "provider",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rules": {
                    "type": "object",
                    "properties": {
                      "outgoing": {
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "mode": {
                                "type": "string",
                                "enum": [
                                  "all"
                                ]
                              }
                            },
                            "required": [
                              "mode"
                            ]
                          },
                          {
                            "type": "object",
                            "properties": {
                              "mode": {
                                "type": "string",
                                "enum": [
                                  "labels"
                                ]
                              },
                              "match": {
                                "type": "string",
                                "enum": [
                                  "any",
                                  "all"
                                ]
                              },
                              "labels": {
                                "type": "array",
                                "items": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 128
                                },
                                "minItems": 1,
                                "maxItems": 50
                              }
                            },
                            "required": [
                              "mode",
                              "match",
                              "labels"
                            ]
                          }
                        ],
                        "description": "Kaneo workspace label IDs controlling task sync. Missing labels pause sync."
                      },
                      "incoming": {
                        "oneOf": [
                          {
                            "type": "object",
                            "properties": {
                              "mode": {
                                "type": "string",
                                "enum": [
                                  "all"
                                ]
                              }
                            },
                            "required": [
                              "mode"
                            ]
                          },
                          {
                            "type": "object",
                            "properties": {
                              "mode": {
                                "type": "string",
                                "enum": [
                                  "labels"
                                ]
                              },
                              "match": {
                                "type": "string",
                                "enum": [
                                  "any",
                                  "all"
                                ]
                              },
                              "labels": {
                                "type": "array",
                                "items": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 128
                                },
                                "minItems": 1,
                                "maxItems": 50
                              }
                            },
                            "required": [
                              "mode",
                              "match",
                              "labels"
                            ]
                          }
                        ],
                        "description": "Exact repository label names controlling which issues can be imported."
                      }
                    },
                    "required": [
                      "outgoing",
                      "incoming"
                    ]
                  }
                },
                "required": [
                  "rules"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Proposed rule impact",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntegrationSyncPreview"
                }
              }
            }
          },
          "400": {
            "description": "Invalid rule or workspace label",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing required permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project, integration, or linked task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration, impact or comparison changed; review again",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/integration-sync/project/{projectId}/{provider}/links/{linkId}/review": {
      "get": {
        "operationId": "reviewIntegrationSyncResume",
        "tags": [
          "Integration sync"
        ],
        "summary": "Compare a paused task with its external issue",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "github",
                "gitea",
                "gitlab"
              ]
            },
            "required": true,
            "name": "provider",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "linkId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Current values and comparison token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntegrationSyncResumePreview"
                }
              }
            }
          },
          "400": {
            "description": "Invalid rule or workspace label",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing required permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project, integration, or linked task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration, impact or comparison changed; review again",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "502": {
            "description": "External issue unavailable; sync remains paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/integration-sync/project/{projectId}/{provider}/links/{linkId}/resume": {
      "post": {
        "operationId": "resumeIntegrationSync",
        "tags": [
          "Integration sync"
        ],
        "summary": "Resume a reviewed issue link",
        "description": "Explicitly choose the current Kaneo or repository title, description and open/closed state. Existing links are reused; historical comments are not replayed. The task must still match its outgoing rule. A changed comparison requires another review.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "github",
                "gitea",
                "gitlab"
              ]
            },
            "required": true,
            "name": "provider",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "linkId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string",
                    "minLength": 64,
                    "maxLength": 64
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "kaneo",
                      "provider"
                    ]
                  }
                },
                "required": [
                  "token",
                  "source"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sync resumed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid rule or workspace label",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing required permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project, integration, or linked task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Comparison changed or synchronization is busy; review or retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "502": {
            "description": "External issue unavailable; sync remains paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/github-integration/app-info": {
      "get": {
        "operationId": "getGitHubAppInfo",
        "tags": [
          "GitHub"
        ],
        "summary": "Get GitHub app info",
        "description": "Get the GitHub App this instance is configured with, so the client can build an install link.",
        "responses": {
          "200": {
            "description": "GitHub app information",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitHubAppInfo"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/github-integration/repositories/{projectId}": {
      "get": {
        "operationId": "listGitHubRepositories",
        "tags": [
          "GitHub"
        ],
        "summary": "List GitHub repositories",
        "description": "List the repositories reachable through the installed GitHub App that the caller administers, for picking one to link. Without `search`, each request reads one page of one installation. With `search`, the server filters by name and description before checking access and keeps scanning later pages and installations within a fixed budget. Follow `nextPage` until it is null to see every repository.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[1-9][0-9]{0,5}$",
              "default": "1"
            },
            "required": false,
            "name": "installationPage",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[1-9][0-9]{0,5}$",
              "default": "1"
            },
            "required": false,
            "name": "repositoryPage",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 100,
              "description": "Case-insensitive match on the repository full name or description. When set, one request scans up to 10 pages of 100 repositories across installations and returns every match it finds, with `nextPage` set to resume the scan."
            },
            "required": false,
            "description": "Case-insensitive match on the repository full name or description. When set, one request scans up to 10 pages of 100 repositories across installations and returns every match it finds, with `nextPage` set to resume the scan.",
            "name": "search",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Repositories reachable through the installed App",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitHubRepositoryList"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/github-integration/verify": {
      "post": {
        "operationId": "verifyGitHubInstallation",
        "tags": [
          "GitHub"
        ],
        "summary": "Verify GitHub installation",
        "description": "Check that the GitHub App is installed on a repository and holds the permissions Kaneo needs. Always 200 -- problems are reported in the body so the client can guide the user.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "repositoryOwner": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "repositoryName": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  }
                },
                "required": [
                  "projectId",
                  "repositoryOwner",
                  "repositoryName"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitHubVerificationResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/github-integration/project/{projectId}": {
      "get": {
        "operationId": "getGitHubIntegration",
        "tags": [
          "GitHub"
        ],
        "summary": "Get GitHub integration",
        "description": "Get the GitHub integration for a project, or null when none is configured.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "GitHub integration details, or null",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitHubIntegration"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createGitHubIntegration",
        "tags": [
          "GitHub"
        ],
        "summary": "Create GitHub integration",
        "description": "Link a project to a GitHub repository. Disconnect first to switch repositories; existing issue and pull request links cannot be reused for another repository.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "repositoryOwner": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "repositoryName": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  }
                },
                "required": [
                  "repositoryOwner",
                  "repositoryName"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedGitHubIntegration"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Integration changed or another repository is already linked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateGitHubIntegration",
        "tags": [
          "GitHub"
        ],
        "summary": "Update GitHub integration",
        "description": "Update the GitHub integration. Omitted fields keep their current value.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "isActive": {
                    "type": "boolean"
                  },
                  "commentTaskLinkOnGitHubIssue": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitHubIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The resulting config failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Integration changed or another repository is already linked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteGitHubIntegration",
        "tags": [
          "GitHub"
        ],
        "summary": "Delete GitHub integration",
        "description": "Unlink a project from its GitHub repository.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The integration was removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitHubDeleteResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or GitHub integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/github-integration/import-issues": {
      "post": {
        "operationId": "importGitHubIssues",
        "tags": [
          "GitHub"
        ],
        "summary": "Import GitHub issues",
        "description": "Import open issues and link open pull requests in bounded steps. Existing tasks are updated. Continue 202 responses with the returned runId until 200; the same runId safely retries completion. Progress is saved after each page. New calls without runId resume an unfinished import or start a new one after completion.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "runId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  }
                },
                "required": [
                  "projectId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Completed import summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssueImportResult"
                }
              }
            }
          },
          "202": {
            "description": "Saved import progress; continue with runId",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssueImportResult"
                }
              }
            }
          },
          "400": {
            "description": "projectId is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:create or task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Integration or import changed; refresh before resuming",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Import busy; retry after one second",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "502": {
            "description": "Provider unavailable or invalid page; progress is saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/gitea-integration/repositories": {
      "post": {
        "operationId": "listGiteaRepositories",
        "tags": [
          "Gitea"
        ],
        "summary": "List Gitea repositories",
        "description": "List the repositories a Gitea token can reach, for picking one to link. Sent as a POST because the token travels in the body rather than the URL.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "baseUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "accessToken": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "projectId",
                  "baseUrl",
                  "accessToken"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accessible repositories",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GiteaRepositoryList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, or invalid Gitea credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/gitea-integration/verify": {
      "post": {
        "operationId": "verifyGiteaAccess",
        "tags": [
          "Gitea"
        ],
        "summary": "Verify Gitea access",
        "description": "Check that the base URL is a Gitea instance and that the token can reach the repository with the permissions Kaneo needs. Repository permission failures are reported in the body; invalid credentials and upstream errors return an error status. Omit accessToken to use the saved token for the unchanged base URL.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "baseUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "accessToken": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Omit to verify with this project's saved token. The base URL must match the saved integration."
                  },
                  "repositoryOwner": {
                    "type": "string",
                    "minLength": 1
                  },
                  "repositoryName": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "projectId",
                  "baseUrl",
                  "repositoryOwner",
                  "repositoryName"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GiteaVerificationResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, or invalid Gitea credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Kaneo authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "500": {
            "description": "Gitea verification failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/gitea-integration/project/{projectId}": {
      "get": {
        "operationId": "getGiteaIntegration",
        "tags": [
          "Gitea"
        ],
        "summary": "Get Gitea integration",
        "description": "Get the Gitea integration for a project, or null when none is configured. The webhook secret is included only for callers with workspace:manage_settings.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Gitea integration details, or null",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GiteaIntegration"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createGiteaIntegration",
        "tags": [
          "Gitea"
        ],
        "summary": "Create Gitea integration",
        "description": "Link a project to a Gitea repository, creating the webhook Gitea will post events to. Use the verify route first to confirm the token really reaches the repository.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "baseUrl": {
                    "type": "string",
                    "minLength": 1
                  },
                  "accessToken": {
                    "type": "string",
                    "description": "Omit to keep the token already stored for this project."
                  },
                  "repositoryOwner": {
                    "type": "string",
                    "minLength": 1
                  },
                  "repositoryName": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "baseUrl",
                  "repositoryOwner",
                  "repositoryName"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GiteaIntegration"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateGiteaIntegration",
        "tags": [
          "Gitea"
        ],
        "summary": "Update Gitea integration",
        "description": "Update the Gitea integration. Omitted fields keep their current value.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "isActive": {
                    "type": "boolean"
                  },
                  "commentTaskLinkOnGiteaIssue": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GiteaIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The resulting config failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Integration changed; refresh before updating settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteGiteaIntegration",
        "tags": [
          "Gitea"
        ],
        "summary": "Delete Gitea integration",
        "description": "Unlink a project from its Gitea repository.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The integration was removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GiteaDeleteResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or Gitea integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/gitea-integration/import-issues": {
      "post": {
        "operationId": "importGiteaIssues",
        "tags": [
          "Gitea"
        ],
        "summary": "Import Gitea issues",
        "description": "Import the linked repository's issues as tasks. Issues that already have a task are refreshed rather than duplicated.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectId": {
                    "type": "string"
                  }
                },
                "required": [
                  "projectId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Import summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GiteaImportResult"
                }
              }
            }
          },
          "400": {
            "description": "projectId is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:create or task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/gitlab-integration/projects": {
      "post": {
        "operationId": "listGitlabProjects",
        "tags": [
          "GitLab"
        ],
        "summary": "List GitLab projects",
        "description": "List the projects a GitLab token is a member of, for picking one to link. Sent as a POST because the token travels in the body rather than the URL.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "baseUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "accessToken": {
                    "type": "string",
                    "minLength": 1
                  },
                  "tokenType": {
                    "type": "string",
                    "enum": [
                      "private",
                      "bearer"
                    ],
                    "description": "`private` sends the token in PRIVATE-TOKEN, which is what personal, project and group access tokens expect. `bearer` sends it in Authorization, for an OAuth2 token you already hold. Defaults to `private`."
                  }
                },
                "required": [
                  "projectId",
                  "baseUrl",
                  "accessToken"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accessible projects",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitlabProjectList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "GitLab rejected the token, or the instance is unreachable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/gitlab-integration/verify": {
      "post": {
        "operationId": "verifyGitlabAccess",
        "tags": [
          "GitLab"
        ],
        "summary": "Verify GitLab access",
        "description": "Check that the base URL is a GitLab instance and that the token can reach the project with the permissions Kaneo needs. An unreachable instance, a missing project, or insufficient permissions are reported in a 200 body so the form can explain them; a token GitLab rejects outright is a 401.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "baseUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "accessToken": {
                    "type": "string",
                    "minLength": 1
                  },
                  "tokenType": {
                    "type": "string",
                    "enum": [
                      "private",
                      "bearer"
                    ],
                    "description": "`private` sends the token in PRIVATE-TOKEN, which is what personal, project and group access tokens expect. `bearer` sends it in Authorization, for an OAuth2 token you already hold. Defaults to `private`."
                  },
                  "projectPath": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Full path of the GitLab project, including any nested groups, for example acme/platform/web."
                  }
                },
                "required": [
                  "projectId",
                  "baseUrl",
                  "accessToken",
                  "projectPath"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitlabVerificationResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "GitLab rejected the token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/gitlab-integration/project/{projectId}": {
      "get": {
        "operationId": "getGitlabIntegration",
        "tags": [
          "GitLab"
        ],
        "summary": "Get GitLab integration",
        "description": "Get the GitLab integration for a project, or null when none is configured. The masked access token and the webhook secret are included only for callers with workspace:manage_settings.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "GitLab integration details, or null",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitlabIntegration"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createGitlabIntegration",
        "tags": [
          "GitLab"
        ],
        "summary": "Create GitLab integration",
        "description": "Link a project to a GitLab project. Kaneo generates the secret GitLab must send with each webhook; add the webhook in GitLab afterwards. Use the verify route first to confirm the token really reaches the project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "baseUrl": {
                    "type": "string",
                    "format": "uri"
                  },
                  "accessToken": {
                    "type": "string",
                    "description": "Omit to keep the stored token only when the GitLab base URL is unchanged. A changed URL requires a new token."
                  },
                  "tokenType": {
                    "type": "string",
                    "enum": [
                      "private",
                      "bearer"
                    ],
                    "description": "`private` sends the token in PRIVATE-TOKEN, which is what personal, project and group access tokens expect. `bearer` sends it in Authorization, for an OAuth2 token you already hold. Defaults to `private`."
                  },
                  "projectPath": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "baseUrl",
                  "projectPath"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitlabIntegration"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "That GitLab project is already linked to another Kaneo project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateGitlabIntegration",
        "tags": [
          "GitLab"
        ],
        "summary": "Update GitLab integration",
        "description": "Update the GitLab integration. Omitted fields keep their current value.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "isActive": {
                    "type": "boolean"
                  },
                  "commentTaskLinkOnGitlabIssue": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitlabIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The resulting config failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Integration changed; refresh before updating settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteGitlabIntegration",
        "tags": [
          "GitLab"
        ],
        "summary": "Delete GitLab integration",
        "description": "Unlink a project from its GitLab project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The integration was removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitlabDeleteResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or GitLab integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/gitlab-integration/import-issues": {
      "post": {
        "operationId": "importGitlabIssues",
        "tags": [
          "GitLab"
        ],
        "summary": "Import GitLab issues",
        "description": "Import the linked project's open issues as tasks. Issues that already have a task are refreshed rather than duplicated. Requires task:create and task:update permissions.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectId": {
                    "type": "string"
                  }
                },
                "required": [
                  "projectId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Import summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitlabImportResult"
                }
              }
            }
          },
          "400": {
            "description": "projectId is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:create or task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/generic-webhook-integration/project/{projectId}": {
      "get": {
        "operationId": "getGenericWebhookIntegration",
        "tags": [
          "Generic Webhook"
        ],
        "summary": "Get webhook integration",
        "description": "Get the outgoing webhook integration for a project, or null when none is configured.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook integration details, or null",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericWebhookIntegration"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createGenericWebhookIntegration",
        "tags": [
          "Generic Webhook"
        ],
        "summary": "Create webhook integration",
        "description": "Create or replace the outgoing webhook for a project. Kaneo POSTs the selected task events to this URL, signed with the secret when one is set.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "webhookUrl": {
                    "type": "string",
                    "minLength": 1
                  },
                  "secret": {
                    "type": "string",
                    "description": "Optional HMAC secret used to sign outgoing deliveries."
                  },
                  "events": {
                    "type": "object",
                    "properties": {
                      "taskCreated": {
                        "type": "boolean"
                      },
                      "taskStatusChanged": {
                        "type": "boolean"
                      },
                      "taskPriorityChanged": {
                        "type": "boolean"
                      },
                      "taskTitleChanged": {
                        "type": "boolean"
                      },
                      "taskDescriptionChanged": {
                        "type": "boolean"
                      },
                      "taskCommentCreated": {
                        "type": "boolean"
                      },
                      "taskDeleted": {
                        "type": "boolean"
                      },
                      "taskMoved": {
                        "type": "boolean"
                      },
                      "taskDueDateChanged": {
                        "type": "boolean"
                      },
                      "taskAssigneeChanged": {
                        "type": "boolean"
                      },
                      "taskUnassigned": {
                        "type": "boolean"
                      },
                      "dueDateReminder": {
                        "type": "boolean"
                      }
                    }
                  },
                  "dueDateReminderLeadTimeMinutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 43200,
                    "description": "Between 5 minutes and 30 days."
                  }
                },
                "required": [
                  "webhookUrl"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericWebhookIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The webhook URL failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateGenericWebhookIntegration",
        "tags": [
          "Generic Webhook"
        ],
        "summary": "Update webhook integration",
        "description": "Update the outgoing webhook. Omitted fields keep their current value, event toggles merge into the existing set, and a null secret clears it.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "webhookUrl": {
                    "type": "string"
                  },
                  "secret": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Send null to remove the signing secret."
                  },
                  "isActive": {
                    "type": "boolean"
                  },
                  "events": {
                    "type": "object",
                    "properties": {
                      "taskCreated": {
                        "type": "boolean"
                      },
                      "taskStatusChanged": {
                        "type": "boolean"
                      },
                      "taskPriorityChanged": {
                        "type": "boolean"
                      },
                      "taskTitleChanged": {
                        "type": "boolean"
                      },
                      "taskDescriptionChanged": {
                        "type": "boolean"
                      },
                      "taskCommentCreated": {
                        "type": "boolean"
                      },
                      "taskDeleted": {
                        "type": "boolean"
                      },
                      "taskMoved": {
                        "type": "boolean"
                      },
                      "taskDueDateChanged": {
                        "type": "boolean"
                      },
                      "taskAssigneeChanged": {
                        "type": "boolean"
                      },
                      "taskUnassigned": {
                        "type": "boolean"
                      },
                      "dueDateReminder": {
                        "type": "boolean"
                      }
                    }
                  },
                  "dueDateReminderLeadTimeMinutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 43200,
                    "description": "Between 5 minutes and 30 days."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenericWebhookIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The resulting config failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or webhook integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteGenericWebhookIntegration",
        "tags": [
          "Generic Webhook"
        ],
        "summary": "Delete webhook integration",
        "description": "Remove the outgoing webhook from a project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The integration was removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or webhook integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/discord-integration/project/{projectId}": {
      "get": {
        "operationId": "getDiscordIntegration",
        "tags": [
          "Discord"
        ],
        "summary": "Get Discord integration",
        "description": "Get the Discord integration for a project, or null when none is configured.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Discord integration details, or null",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscordIntegration"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createDiscordIntegration",
        "tags": [
          "Discord"
        ],
        "summary": "Create Discord integration",
        "description": "Create or replace the Discord integration for a project. The webhook URL is checked for shape only; delivery failures surface later.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "webhookUrl": {
                    "type": "string",
                    "minLength": 1
                  },
                  "channelName": {
                    "type": "string"
                  },
                  "events": {
                    "type": "object",
                    "properties": {
                      "taskCreated": {
                        "type": "boolean"
                      },
                      "taskStatusChanged": {
                        "type": "boolean"
                      },
                      "taskPriorityChanged": {
                        "type": "boolean"
                      },
                      "taskTitleChanged": {
                        "type": "boolean"
                      },
                      "taskDescriptionChanged": {
                        "type": "boolean"
                      },
                      "taskCommentCreated": {
                        "type": "boolean"
                      }
                    }
                  }
                },
                "required": [
                  "webhookUrl"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscordIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The webhook URL failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateDiscordIntegration",
        "tags": [
          "Discord"
        ],
        "summary": "Update Discord integration",
        "description": "Update the Discord integration. Omitted fields keep their current value, and event toggles are merged into the existing set.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "webhookUrl": {
                    "type": "string"
                  },
                  "channelName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "isActive": {
                    "type": "boolean"
                  },
                  "events": {
                    "type": "object",
                    "properties": {
                      "taskCreated": {
                        "type": "boolean"
                      },
                      "taskStatusChanged": {
                        "type": "boolean"
                      },
                      "taskPriorityChanged": {
                        "type": "boolean"
                      },
                      "taskTitleChanged": {
                        "type": "boolean"
                      },
                      "taskDescriptionChanged": {
                        "type": "boolean"
                      },
                      "taskCommentCreated": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscordIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The resulting config failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or Discord integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteDiscordIntegration",
        "tags": [
          "Discord"
        ],
        "summary": "Delete Discord integration",
        "description": "Remove the Discord integration from a project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The integration was removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or Discord integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/mattermost-integration/project/{projectId}": {
      "get": {
        "operationId": "getMattermostIntegration",
        "tags": [
          "Mattermost"
        ],
        "summary": "Get Mattermost integration",
        "description": "Get the Mattermost integration for a project, or null when none is configured.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Mattermost integration details, or null",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MattermostIntegration"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createMattermostIntegration",
        "tags": [
          "Mattermost"
        ],
        "summary": "Create Mattermost integration",
        "description": "Create or replace the Mattermost integration for a project. The webhook URL must use HTTPS and pass destination validation; delivery failures surface later.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "webhookUrl": {
                    "type": "string",
                    "minLength": 1
                  },
                  "channelName": {
                    "type": "string"
                  },
                  "events": {
                    "type": "object",
                    "properties": {
                      "taskCreated": {
                        "type": "boolean"
                      },
                      "taskStatusChanged": {
                        "type": "boolean"
                      },
                      "taskPriorityChanged": {
                        "type": "boolean"
                      },
                      "taskTitleChanged": {
                        "type": "boolean"
                      },
                      "taskDescriptionChanged": {
                        "type": "boolean"
                      },
                      "taskCommentCreated": {
                        "type": "boolean"
                      }
                    }
                  }
                },
                "required": [
                  "webhookUrl"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MattermostIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The webhook URL failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateMattermostIntegration",
        "tags": [
          "Mattermost"
        ],
        "summary": "Update Mattermost integration",
        "description": "Update the Mattermost integration. Omitted fields keep their current value, and event toggles are merged into the existing set.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "webhookUrl": {
                    "type": "string"
                  },
                  "channelName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "isActive": {
                    "type": "boolean"
                  },
                  "events": {
                    "type": "object",
                    "properties": {
                      "taskCreated": {
                        "type": "boolean"
                      },
                      "taskStatusChanged": {
                        "type": "boolean"
                      },
                      "taskPriorityChanged": {
                        "type": "boolean"
                      },
                      "taskTitleChanged": {
                        "type": "boolean"
                      },
                      "taskDescriptionChanged": {
                        "type": "boolean"
                      },
                      "taskCommentCreated": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MattermostIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The resulting config failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or Mattermost integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteMattermostIntegration",
        "tags": [
          "Mattermost"
        ],
        "summary": "Delete Mattermost integration",
        "description": "Remove the Mattermost integration from a project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The integration was removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or Mattermost integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/slack-integration/project/{projectId}": {
      "get": {
        "operationId": "getSlackIntegration",
        "tags": [
          "Slack"
        ],
        "summary": "Get Slack integration",
        "description": "Get the Slack integration for a project, or null when none is configured.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Slack integration details, or null",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SlackIntegration"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createSlackIntegration",
        "tags": [
          "Slack"
        ],
        "summary": "Create Slack integration",
        "description": "Create or replace the Slack integration for a project. The webhook URL is checked for shape only; delivery failures surface later.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "webhookUrl": {
                    "type": "string",
                    "minLength": 1
                  },
                  "channelName": {
                    "type": "string"
                  },
                  "events": {
                    "type": "object",
                    "properties": {
                      "taskCreated": {
                        "type": "boolean"
                      },
                      "taskStatusChanged": {
                        "type": "boolean"
                      },
                      "taskPriorityChanged": {
                        "type": "boolean"
                      },
                      "taskTitleChanged": {
                        "type": "boolean"
                      },
                      "taskDescriptionChanged": {
                        "type": "boolean"
                      },
                      "taskCommentCreated": {
                        "type": "boolean"
                      }
                    }
                  }
                },
                "required": [
                  "webhookUrl"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SlackIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The webhook URL failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateSlackIntegration",
        "tags": [
          "Slack"
        ],
        "summary": "Update Slack integration",
        "description": "Update the Slack integration. Omitted fields keep their current value, and event toggles are merged into the existing set.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "webhookUrl": {
                    "type": "string"
                  },
                  "channelName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "isActive": {
                    "type": "boolean"
                  },
                  "events": {
                    "type": "object",
                    "properties": {
                      "taskCreated": {
                        "type": "boolean"
                      },
                      "taskStatusChanged": {
                        "type": "boolean"
                      },
                      "taskPriorityChanged": {
                        "type": "boolean"
                      },
                      "taskTitleChanged": {
                        "type": "boolean"
                      },
                      "taskDescriptionChanged": {
                        "type": "boolean"
                      },
                      "taskCommentCreated": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SlackIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The resulting config failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or Slack integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteSlackIntegration",
        "tags": [
          "Slack"
        ],
        "summary": "Delete Slack integration",
        "description": "Remove the Slack integration from a project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The integration was removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or Slack integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/telegram-integration/project/{projectId}": {
      "get": {
        "operationId": "getTelegramIntegration",
        "tags": [
          "Telegram"
        ],
        "summary": "Get Telegram integration",
        "description": "Get the Telegram integration for a project, or null when none is configured.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Telegram integration details, or null",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TelegramIntegration"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "createTelegramIntegration",
        "tags": [
          "Telegram"
        ],
        "summary": "Create Telegram integration",
        "description": "Create or replace the Telegram integration for a project. The bot token and chat are checked for shape only, not against Telegram.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "botToken": {
                    "type": "string",
                    "minLength": 1,
                    "description": "A Telegram bot token, in the form 123456789:AA..."
                  },
                  "chatId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "threadId": {
                    "type": "number"
                  },
                  "chatLabel": {
                    "type": "string"
                  },
                  "events": {
                    "type": "object",
                    "properties": {
                      "taskCreated": {
                        "type": "boolean"
                      },
                      "taskStatusChanged": {
                        "type": "boolean"
                      },
                      "taskPriorityChanged": {
                        "type": "boolean"
                      },
                      "taskTitleChanged": {
                        "type": "boolean"
                      },
                      "taskDescriptionChanged": {
                        "type": "boolean"
                      },
                      "taskCommentCreated": {
                        "type": "boolean"
                      }
                    }
                  }
                },
                "required": [
                  "botToken",
                  "chatId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TelegramIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The bot token or chat failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "patch": {
        "operationId": "updateTelegramIntegration",
        "tags": [
          "Telegram"
        ],
        "summary": "Update Telegram integration",
        "description": "Update the Telegram integration. Omitted fields keep their current value; threadId and chatLabel accept null to clear them.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "botToken": {
                    "type": "string"
                  },
                  "chatId": {
                    "type": "string"
                  },
                  "threadId": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "chatLabel": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "isActive": {
                    "type": "boolean"
                  },
                  "events": {
                    "type": "object",
                    "properties": {
                      "taskCreated": {
                        "type": "boolean"
                      },
                      "taskStatusChanged": {
                        "type": "boolean"
                      },
                      "taskPriorityChanged": {
                        "type": "boolean"
                      },
                      "taskTitleChanged": {
                        "type": "boolean"
                      },
                      "taskDescriptionChanged": {
                        "type": "boolean"
                      },
                      "taskCommentCreated": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated integration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TelegramIntegration"
                }
              }
            }
          },
          "400": {
            "description": "The resulting config failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or Telegram integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteTelegramIntegration",
        "tags": [
          "Telegram"
        ],
        "summary": "Delete Telegram integration",
        "description": "Remove the Telegram integration from a project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The integration was removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing workspace:manage_settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project or Telegram integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task-relation/{taskId}": {
      "get": {
        "operationId": "getTaskRelations",
        "tags": [
          "Task Relations"
        ],
        "summary": "Get task relations",
        "description": "Get every relation where the task is the source or the target, each with a summary of both linked tasks. Relations pointing outside the caller's workspace are omitted.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Task relations with the linked task summaries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TaskRelationWithTasks"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task-relation": {
      "post": {
        "operationId": "createTaskRelation",
        "tags": [
          "Task Relations"
        ],
        "summary": "Create task relation",
        "description": "Link two tasks. Authorization is scoped to the source task's workspace.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sourceTaskId": {
                    "type": "string"
                  },
                  "targetTaskId": {
                    "type": "string"
                  },
                  "relationType": {
                    "type": "string",
                    "enum": [
                      "subtask",
                      "blocks",
                      "related"
                    ]
                  }
                },
                "required": [
                  "sourceTaskId",
                  "targetTaskId",
                  "relationType"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created relation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskRelation"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Source or target task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "This relation already exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/task-relation/{id}": {
      "delete": {
        "operationId": "deleteTaskRelation",
        "tags": [
          "Task Relations"
        ],
        "summary": "Delete task relation",
        "description": "Remove a link between two tasks. Returns the deleted relation.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted relation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskRelation"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task relation not found, or its source task is gone",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/external-link/task/{taskId}": {
      "post": {
        "operationId": "createExternalLink",
        "tags": [
          "External Links"
        ],
        "summary": "Create an external link",
        "description": "Add a resource link to a task",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "An HTTP or HTTPS URL"
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  }
                },
                "required": [
                  "url"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "External link created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedExternalLink"
                }
              }
            }
          },
          "400": {
            "description": "Invalid external link data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No permission to update the task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "get": {
        "operationId": "getExternalLinksByTask",
        "tags": [
          "External Links"
        ],
        "summary": "Get task external links",
        "description": "Get manually added resource links and links from connected integrations, such as GitHub or Gitea issues.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "External links for the task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ExternalLink"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/external-link/task/{taskId}/{id}": {
      "delete": {
        "operationId": "deleteExternalLink",
        "tags": [
          "External Links"
        ],
        "summary": "Remove a manual resource link",
        "description": "Remove a manually added link from a task. Integration-managed links cannot be removed through this endpoint.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Resource link removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedExternalLink"
                }
              }
            }
          },
          "400": {
            "description": "Invalid link data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No permission to update the task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task or manual resource link not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/workflow-rule/{projectId}": {
      "get": {
        "operationId": "getWorkflowRules",
        "tags": [
          "Workflow Rules"
        ],
        "summary": "Get workflow rules",
        "description": "Get every workflow rule for a project. A rule moves a task to a column when an integration event fires.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "List of workflow rules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WorkflowRule"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "put": {
        "operationId": "upsertWorkflowRule",
        "tags": [
          "Workflow Rules"
        ],
        "summary": "Upsert workflow rule",
        "description": "Create a workflow rule, or update the target column of the existing rule for the same integration and event.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "integrationType": {
                    "type": "string"
                  },
                  "eventType": {
                    "type": "string",
                    "enum": [
                      "branch_push",
                      "pr_opened",
                      "pr_merged",
                      "issue_opened",
                      "issue_closed",
                      "issue_reopened"
                    ]
                  },
                  "columnId": {
                    "type": "string"
                  }
                },
                "required": [
                  "integrationType",
                  "eventType",
                  "columnId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created or updated rule",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowRuleRow"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/workflow-rule/{id}": {
      "delete": {
        "operationId": "deleteWorkflowRule",
        "tags": [
          "Workflow Rules"
        ],
        "summary": "Delete workflow rule",
        "description": "Delete a workflow rule. Returns the rule that was removed.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted rule",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowRuleRow"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workflow rule not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/invitation/pending": {
      "get": {
        "operationId": "getUserPendingInvitations",
        "tags": [
          "Invitations"
        ],
        "summary": "Get pending invitations",
        "description": "Get the current user's unexpired, unaccepted invitations. Returns an empty list until the user's email is verified.",
        "responses": {
          "200": {
            "description": "List of pending invitations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PendingInvitation"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/invitation/{id}": {
      "get": {
        "operationId": "getInvitationDetails",
        "tags": [
          "Invitations"
        ],
        "summary": "Get invitation details",
        "description": "Look up an invitation by ID. Always 200 -- an unusable invitation is reported with valid: false and a reason rather than an error status.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Invitation details",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvitationDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/workspace": {
      "get": {
        "operationId": "listWorkspaces",
        "tags": [
          "Workspaces"
        ],
        "summary": "List workspaces",
        "description": "List the workspaces where one of the caller's roles grants workspace:read, with the caller's role, sorted by name. Instance admins get every workspace they are a member of, and only those.",
        "responses": {
          "200": {
            "description": "The caller's workspaces",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Workspace"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks workspace:read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/workspace/{workspaceId}": {
      "get": {
        "operationId": "getWorkspace",
        "tags": [
          "Workspaces"
        ],
        "summary": "Get workspace",
        "description": "Get a workspace where one of the caller's roles grants workspace:read, with the caller's role. Instance admins can get any workspace, with a null role when they are not a member.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks workspace:read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found, the caller is not a member of it, or none of their roles grants workspace:read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/workspace/{workspaceId}/members": {
      "get": {
        "operationId": "getWorkspaceMembers",
        "tags": [
          "Workspaces"
        ],
        "summary": "Get workspace members",
        "description": "Get all members of a workspace, with their role. Pass projectId to get only the members who can access that project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "description": "Only return members who can access this project, for example to fill an assignee picker."
            },
            "required": false,
            "description": "Only return members who can access this project, for example to fill an assignee picker.",
            "name": "projectId",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "List of workspace members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WorkspaceMember"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found, or project not found in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/workspace/{workspaceId}/project-access/me": {
      "get": {
        "operationId": "getMyProjectAccess",
        "tags": [
          "Workspaces"
        ],
        "summary": "Get my project access",
        "description": "Get the caller's own project access in the workspace: every project, or the selected projects they're limited to. Owners and instance admins always get every project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's project access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberProjectAccess"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/workspace/{workspaceId}/capabilities": {
      "get": {
        "operationId": "getMyWorkspaceCapabilities",
        "tags": [
          "Workspaces"
        ],
        "summary": "Get my workspace capabilities",
        "description": "Check every named workspace action for the caller in one request: which of them the caller's roles allow, narrowed by the API key's scope when one is used. Instance admins are allowed everything.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's capabilities in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceCapabilities"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/workspace/{workspaceId}/project-access": {
      "get": {
        "operationId": "getWorkspaceProjectAccess",
        "tags": [
          "Workspaces"
        ],
        "summary": "Get member project access",
        "description": "List the members who are limited to selected projects, with the projects each one can access. Everyone else in the workspace can access every project. A caller whose own access is limited only sees the projects they can access.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Members limited to selected projects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MemberProjectAccess"
                  },
                  "description": "Members limited to selected projects. Members not listed can access every project. Project IDs only include projects the caller can access."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing member:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/workspace/{workspaceId}/members/{userId}/project-access": {
      "put": {
        "operationId": "updateMemberProjectAccess",
        "tags": [
          "Workspaces"
        ],
        "summary": "Update member project access",
        "description": "Give a member access to every project, or limit them to selected projects. Owners always access every project, and you can't change your own access or give access to projects you can't access yourself.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "userId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectAccess": {
                    "type": "string",
                    "enum": [
                      "all",
                      "selected"
                    ],
                    "description": "\"all\" gives access to every project in the workspace, including future ones. \"selected\" limits the member to projectIds."
                  },
                  "projectIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "default": [],
                    "description": "Projects the member can access when projectAccess is \"selected\"."
                  }
                },
                "required": [
                  "projectAccess"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The member's project access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberProjectAccess"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, the member is an owner, or a project is not in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing member:update permission, changing your own access, or granting a project you can't access",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace or member not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/custom-field/project/{projectId}": {
      "get": {
        "operationId": "getCustomFieldsByProject",
        "tags": [
          "Custom Fields"
        ],
        "summary": "Get custom fields",
        "description": "Get all custom field definitions for a project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "List of custom field definitions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CustomFieldDefinition"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing read permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/custom-field/project/{projectId}/values": {
      "get": {
        "operationId": "getCustomFieldValuesByProject",
        "tags": [
          "Custom Fields"
        ],
        "summary": "Get project custom field values",
        "description": "Get all custom field values for every task in a project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "List of custom field values for the project",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CustomFieldValue"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing read permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/custom-field/task/{taskId}": {
      "get": {
        "operationId": "getCustomFieldValuesByTask",
        "tags": [
          "Custom Fields"
        ],
        "summary": "Get task custom field values",
        "description": "Get all custom field values for a task with their definitions.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "taskId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "List of custom field values for the task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CustomFieldValue"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:read permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/custom-field/project/{projectId}/filter-values": {
      "get": {
        "operationId": "getCustomFieldFilterValues",
        "tags": [
          "Custom Fields"
        ],
        "summary": "Get custom field filter values",
        "description": "Get distinct values used by tasks for each custom field of a project.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Distinct values used for each custom field",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CustomFieldFilterValues"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing read permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/custom-field": {
      "post": {
        "operationId": "createCustomField",
        "tags": [
          "Custom Fields"
        ],
        "summary": "Create custom field",
        "description": "Create a custom field definition for a project.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "projectId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "text",
                      "number",
                      "date",
                      "dropdown",
                      "boolean",
                      "multiselect"
                    ]
                  },
                  "required": {
                    "type": "boolean",
                    "default": false
                  },
                  "defaultValue": {
                    "type": "string"
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "projectId",
                  "name",
                  "type"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created custom field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomFieldDefinition"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/custom-field/reorder/{projectId}": {
      "put": {
        "operationId": "reorderCustomFields",
        "tags": [
          "Custom Fields"
        ],
        "summary": "Reorder custom fields",
        "description": "Set new positions for a project's custom fields.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "projectId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fields": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "position": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "id",
                        "position"
                      ]
                    }
                  }
                },
                "required": [
                  "fields"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The reordered custom fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReorderCustomFieldsResponse"
                }
              }
            }
          },
          "400": {
            "description": "A custom field does not belong to this project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/custom-field/value": {
      "put": {
        "operationId": "setCustomFieldValue",
        "tags": [
          "Custom Fields"
        ],
        "summary": "Set custom field value",
        "description": "Create or update a custom field value for a task.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "taskId": {
                    "type": "string"
                  },
                  "fieldId": {
                    "type": "string"
                  },
                  "value": {
                    "type": "string"
                  }
                },
                "required": [
                  "taskId",
                  "fieldId",
                  "value"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved custom field value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetCustomFieldValueResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, or unknown custom field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing task:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/custom-field/{id}": {
      "delete": {
        "operationId": "deleteCustomField",
        "tags": [
          "Custom Fields"
        ],
        "summary": "Delete custom field",
        "description": "Delete a custom field definition by ID.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The deleted custom field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomFieldDefinition"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Custom field not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "put": {
        "operationId": "updateCustomField",
        "tags": [
          "Custom Fields"
        ],
        "summary": "Update custom field",
        "description": "Update a custom field's name, required flag, default, or options. The type can't change, except between dropdown and multiselect. Options that tasks still use can't be removed or renamed; the request is rejected instead of changing task values.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "New name for the field. Cannot be blank."
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "text",
                      "number",
                      "date",
                      "dropdown",
                      "boolean",
                      "multiselect"
                    ],
                    "description": "Only `dropdown` and `multiselect` can switch between each other. Task values are converted, and switching to `dropdown` is rejected while a task has more than one option picked. Any other type change is rejected."
                  },
                  "required": {
                    "type": "boolean",
                    "description": "Whether every task must have a value. Turning it on needs a default, which fills tasks that have no value yet."
                  },
                  "defaultValue": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Value new tasks start with. Send `null` or an empty string to clear it. Multiselect defaults are a JSON array of options."
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "The full option list for dropdown and multiselect fields, in display order. Adding and reordering options is always allowed. Removing or renaming an option that a task still uses is rejected with 400, so task values are never changed silently."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated custom field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomFieldDefinition"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, a type change, or removing an option that tasks still use",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "No access to the project, or missing project:update permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Custom field not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/import/projects": {
      "post": {
        "operationId": "listImportSourceProjects",
        "tags": [
          "Import"
        ],
        "summary": "List projects in an import source",
        "description": "Sign in to PLANKA, Plane, Linear, Jira, Trello or another Kaneo instance and list the projects that can be imported. The credentials are used only for this request; they are never stored, logged, or returned.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspaceId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "connection": {
                    "$ref": "#/components/schemas/ImportConnection"
                  }
                },
                "required": [
                  "workspaceId",
                  "connection"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Projects available in the source",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportSourceProjectList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, credentials the source rejected, or a destination this server may not reach",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "402": {
            "description": "Workspace plan has expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing workspace:manage_settings or project:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          },
          "502": {
            "description": "The source could not be reached or answered with an error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/import/runs": {
      "post": {
        "operationId": "startImportRun",
        "tags": [
          "Import"
        ],
        "summary": "Start an import",
        "description": "Import the selected source projects into the workspace as new Kaneo projects. The import runs in the background; poll getImportRun for progress. The credentials are held in memory only while the import runs; they are never stored, logged, or returned. Imported tasks do not send notifications or trigger integrations.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspaceId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "connection": {
                    "$ref": "#/components/schemas/ImportConnection"
                  },
                  "projectIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 512
                    },
                    "minItems": 1,
                    "maxItems": 50,
                    "description": "Source project IDs from listImportSourceProjects, imported in this order."
                  },
                  "includeComments": {
                    "type": "boolean",
                    "default": true,
                    "description": "Import task comments too. Defaults to true."
                  }
                },
                "required": [
                  "workspaceId",
                  "connection",
                  "projectIds"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The import started",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportRunEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, credentials the source rejected, a destination this server may not reach, or a selected project the source no longer lists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "402": {
            "description": "Workspace plan has expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing workspace:manage_settings or project:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Another import is already running in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "This server is already running its maximum number of imports, or the API key exceeded its rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "502": {
            "description": "The source could not be reached or answered with an error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "The server is restarting and not starting new imports",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/import/runs/{workspaceId}": {
      "get": {
        "operationId": "listImportRuns",
        "tags": [
          "Import"
        ],
        "summary": "List imports",
        "description": "The workspace's 20 most recent imports, newest first. A run whose server stopped reporting progress is returned as failed.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Recent imports",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportRunList"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing workspace:manage_settings or project:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/import/trello/authorize-url/{workspaceId}": {
      "get": {
        "operationId": "getTrelloAuthorizeUrl",
        "tags": [
          "Import"
        ],
        "summary": "Get the Trello authorize link",
        "description": "Link to Trello's authorize page for this server's Trello app, where the user approves read access and copies a token for startImportRun. null when the server has no TRELLO_API_KEY.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The Trello authorize link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrelloAuthorizeUrl"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing workspace:manage_settings or project:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/import/runs/{workspaceId}/{runId}": {
      "get": {
        "operationId": "getImportRun",
        "tags": [
          "Import"
        ],
        "summary": "Get an import",
        "description": "Progress and outcome of one import, including each project's status, counts and warnings.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "runId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The import",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportRunEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "Missing workspace:manage_settings or project:create permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace or import not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/user/me": {
      "get": {
        "operationId": "getCurrentUser",
        "tags": [
          "User"
        ],
        "summary": "Get current user",
        "description": "Return the currently authenticated user.",
        "responses": {
          "200": {
            "description": "Current user",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrentUser"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/user/avatar": {
      "put": {
        "operationId": "uploadUserAvatar",
        "tags": [
          "User"
        ],
        "summary": "Upload avatar",
        "description": "Store a base64 encoded avatar (PNG, JPEG, or WebP, up to 512KB) for the current user and return its public URL. Replaces any existing avatar.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contentType": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "One of image/png, image/jpeg, or image/webp.",
                    "example": "image/png"
                  },
                  "data": {
                    "type": "string",
                    "maxLength": 700076,
                    "description": "Base64 encoded image bytes."
                  }
                },
                "required": [
                  "contentType",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Avatar stored",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserAvatar"
                }
              }
            }
          },
          "400": {
            "description": "Unsupported content type, malformed base64, or too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "408": {
            "description": "Request body timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "413": {
            "description": "Request body exceeds the upload limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteUserAvatar",
        "tags": [
          "User"
        ],
        "summary": "Delete avatar",
        "description": "Remove the uploaded avatar of the current user. Succeeds even when there was nothing to remove.",
        "responses": {
          "200": {
            "description": "Avatar removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserAvatarDeleted"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/admin/users": {
      "get": {
        "operationId": "listAdminUsers",
        "tags": [
          "Admin"
        ],
        "summary": "List instance users",
        "description": "Page through every user account on this instance, optionally filtered by a case-insensitive name or email search. Requires an instance administrator session; API keys are rejected.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Case-insensitive match against user name or email."
            },
            "required": false,
            "description": "Case-insensitive match against user name or email.",
            "name": "search",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Users matching the search",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid search, page, or limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not an instance administrator or is authenticated with an API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/admin/workspaces": {
      "get": {
        "operationId": "listAdminWorkspaces",
        "tags": [
          "Admin"
        ],
        "summary": "List instance workspaces",
        "description": "Page through every workspace on this instance with its owners, member count and project count, optionally filtered by a case-insensitive name or slug search. Requires an instance administrator session; API keys are rejected.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Case-insensitive match against workspace name or slug."
            },
            "required": false,
            "description": "Case-insensitive match against workspace name or slug.",
            "name": "search",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Workspaces matching the search",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminWorkspaceList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid search, page, or limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not an instance administrator or is authenticated with an API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/admin/workspaces/{workspaceId}/members": {
      "get": {
        "operationId": "listAdminWorkspaceMembers",
        "tags": [
          "Admin"
        ],
        "summary": "List workspace members",
        "description": "List every member of a workspace with their role, whether or not the administrator belongs to it.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Workspace members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminWorkspaceMember"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not an instance administrator or is authenticated with an API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      },
      "post": {
        "operationId": "addAdminWorkspaceMember",
        "tags": [
          "Admin"
        ],
        "summary": "Add a user to a workspace",
        "description": "Add an existing user to a workspace with a role. They start with access to every project. Returns the updated member list.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "userId": {
                    "type": "string",
                    "minLength": 1
                  },
                  "role": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "description": "A built-in role (viewer, member, admin) or one of the workspace's custom roles. Use the ownership endpoint to make someone the owner."
                  }
                },
                "required": [
                  "userId",
                  "role"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated workspace members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminWorkspaceMember"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown role, or the owner role was requested",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not an instance administrator or is authenticated with an API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace or user not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The user is already a member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/admin/workspaces/{workspaceId}/roles": {
      "get": {
        "operationId": "listAdminWorkspaceRoles",
        "tags": [
          "Admin"
        ],
        "summary": "List assignable workspace roles",
        "description": "List the roles an administrator can give members of a workspace: the built-in viewer, member and admin roles plus the workspace's custom roles. Ownership is changed with the ownership transfer instead.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Assignable role names",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Role names an administrator can assign in the workspace."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not an instance administrator or is authenticated with an API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/admin/workspaces/{workspaceId}/members/{userId}/role": {
      "put": {
        "operationId": "updateAdminWorkspaceMemberRole",
        "tags": [
          "Admin"
        ],
        "summary": "Change a workspace member's role",
        "description": "Change a member's role. Use the ownership transfer to make someone the owner; the only owner's role can't be changed. Returns the updated member list.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "userId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "description": "A built-in role (viewer, member, admin) or one of the workspace's custom roles. Use the ownership endpoint to make someone the owner."
                  }
                },
                "required": [
                  "role"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated workspace members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminWorkspaceMember"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown role, or the owner role was requested",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not an instance administrator or is authenticated with an API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace or member not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The member is the only owner",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/admin/workspaces/{workspaceId}/members/{userId}": {
      "delete": {
        "operationId": "removeAdminWorkspaceMember",
        "tags": [
          "Admin"
        ],
        "summary": "Remove a workspace member",
        "description": "Remove a member from a workspace. Their account stays. The only owner can't be removed. Returns the updated member list.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "userId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Updated workspace members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminWorkspaceMember"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not an instance administrator or is authenticated with an API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace or member not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The member is the only owner",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    },
    "/admin/workspaces/{workspaceId}/owner": {
      "put": {
        "operationId": "transferAdminWorkspaceOwnership",
        "tags": [
          "Admin"
        ],
        "summary": "Transfer workspace ownership",
        "description": "Make an existing member the only owner of a workspace, for example when the previous owner has left. Previous owners become admins. Returns the updated member list.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "workspaceId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "userId": {
                    "type": "string",
                    "minLength": 1,
                    "description": "An existing member who becomes the only owner. Previous owners become admins."
                  }
                },
                "required": [
                  "userId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated workspace members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminWorkspaceMember"
                  }
                }
              }
            }
          },
          "400": {
            "description": "The member is already the only owner",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not an instance administrator or is authenticated with an API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Workspace or member not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/ApiKeyRateLimited"
          }
        }
      }
    }
  },
  "webhooks": {}
}

---

--- METRICS ---
- Files Extracted: 26
- Estimated Token Budget: ~205791 tokens
- Canonical Reference: https://codewiki.google/github.com/usekaneo/kaneo
