---
summary: "Planka v2.1.1 API and Docker behaviors that differ from or are absent in its docs — auth/PAT limits, first-login terms flow, card-move and project-delete quirks, container uid — plus service-account credentials location for planka.hyperthrive.io."
tags:
  - type/reference
  - tool/planka
  - project/backlog-pilot
  - domain/self-hosting
scope: global
source: ovh-prod Planka deploy, 2026-07-08
date: 2026-07-08
last_updated: 2026-07-10
---

# Planka v2 API Gotchas (verified on v2.1.1)

Discovered during the ovh-prod deploy (2026-07-08), verified against a live v2.1.1 instance.
Directly relevant to the `backlog` CLI in `~/dev/ruby-gems` and any Planka automation.

1. **No scoped API keys / PATs.** Only `POST /api/access-tokens` with
   `{emailOrUsername, password}` → bearer JWT. Upstream issue
   [#945](https://github.com/plankanban/planka/issues/945) still open as of v2.1.1.
   Automation must hold real user credentials; a scoped-token proxy is the workaround.
2. **First-ever login requires terms acceptance.** The first `POST /api/access-tokens`
   returns `E_FORBIDDEN` with a `pendingToken` and `step: "accept-terms"`. Flow:
   `GET /api/terms` → take `item.signature` → `POST /api/access-tokens/accept-terms`
   with `{pendingToken, signature}` → real token. Subsequent logins are normal.
3. **Card move needs both fields.** `PATCH /api/cards/:id` requires **both** `listId`
   and `position`; `listId` alone returns `E_MISSING_OR_INVALID_PARAMS`.
4. **Project delete is not cascading.** `DELETE /api/projects/:id` returns 422
   `"Must not have boards"` — delete each board first. The web UI hits the same
   constraint but **fails silently**: the project vanishes locally, then reappears on
   reload with no error shown (confirmed 2026-07-10 on the personal-account project).
   A non-manager attempting the delete gets a 404 (existence hidden, same as gotcha #6).
   Escape hatch when no manager creds are at hand: insert a row into `project_manager`
   directly in Postgres (`docker exec planka-postgres psql -U planka`), then delete
   boards + project via the API as that user.
5. **Sails literal-alias routes.** Some delete routes embed a literal parameter-name
   segment: card-label detach is `DELETE /api/cards/:cardId/card-labels/labelId::labelId`
   (the path literally contains `labelId:` before the id). Normal REST-shaped guesses 404.
   When a route 404s unexpectedly, check `server/config/routes.js` in the Planka source.
   Related: `GET /api/tasks/:id` doesn't exist (404s to SPA HTML — check Content-Type);
   cross-board card move needs `boardId` in the PATCH on top of `listId`+`position`.
6. **Webhooks are instance-level and admin-only.** `GET/POST /api/webhooks` etc.; a
   non-admin gets 404 (Sails hides existence, not 403). Not board/project-scoped;
   `events` are comma-separated strings in, arrays out.
7. **Attachment download auth differs.** `GET /attachments/:id/download/:filename`
   (outside `/api`) authenticates via `Cookie: accessToken=<jwt>` (or `x-api-key`) —
   `Authorization: Bearer` 401s on this route. Also means VCR/webmock setups must
   filter Cookie headers, not just Authorization, or the JWT leaks into cassettes.
8. **Container uid.** The `ghcr.io/plankanban/planka` image runs as uid 1000 (`node`).
   A bind-mounted `/app/data` must be `chown 1000` or attachment/avatar uploads fail
   (container starts healthy either way — the failure is silent until first upload).

## Service accounts (planka.hyperthrive.io)

Created 2026-07-08 because of gotcha #1 (no PATs — automation holds real user creds).
Credentials live on the OVH server (ssh alias `ovh-vps`) at
`~/services/planka/bot-credentials.env` (mode 600); admin creds remain in
`~/services/planka/.env`. Copy to Bitwarden.

- `planka_bot` — role `projectOwner`; for the planka-api gem / `backlog` CLI / cron tick.
  Env vars: `PLANKA_BASE_URL`, `PLANKA_USERNAME`, `PLANKA_PASSWORD`.
- `hermes` — role `boardUser` (deliberately restricted; Hermes agent is less trusted).
  Env vars: `HERMES_PLANKA_USERNAME`, `HERMES_PLANKA_PASSWORD`.
- Usernames must match `/^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/` — no hyphens
  (hence `planka_bot`, not `planka-bot`).

Deploy as-built details live in the repo: `ovh-prod/docs/planka-deploy-prd.md`.
Related: [[vault-backlog-pilot-plan]], [[backlog-system-options-research]].
