Files
pcli/openspec/changes/archive/2026-02-09-planka-cli-v1/specs/api-client/spec.md
T
Steve Cliff b07572fed5 Released v1
2026-02-12 10:37:19 +00:00

11 KiB

ADDED Requirements

Requirement: Base HTTP client

The system SHALL provide a base HTTP client that sends requests to the Planka API. The client SHALL construct URLs by joining the configured base URL with the API path. The client SHALL attach an Authorization: Bearer <token> header to every request. The client SHALL send and receive JSON (Content-Type: application/json). The client SHALL accept a *slog.Logger and log every request at DEBUG level with method, path, status code, and duration. The client SHALL log errors at WARN level.

Scenario: Successful API request

  • WHEN the client sends a request to a valid endpoint
  • THEN the response body SHALL be returned as parsed JSON
  • AND the request SHALL include the bearer token header
  • AND a DEBUG log entry SHALL be emitted with method, path, status, and duration

Scenario: API returns error status

  • WHEN the API responds with a 4xx or 5xx status code
  • THEN the client SHALL return an APIError containing the HTTP status code and response message
  • AND a WARN log entry SHALL be emitted

Scenario: Network failure

  • WHEN the HTTP request fails due to a network error (connection refused, timeout, DNS failure)
  • THEN the client SHALL return a Go error wrapping the underlying network error

Requirement: Authentication from environment

The system SHALL read PLANKA_URL from the environment to determine the API base URL. The system SHALL read PLANKA_TOKEN from the environment to determine the bearer token. Global flags --url and --token SHALL override the corresponding environment variables. Flag values SHALL take precedence over environment variables.

Scenario: Auth from environment variables

  • WHEN PLANKA_URL and PLANKA_TOKEN are set in the environment
  • AND no --url or --token flags are provided
  • THEN the client SHALL use the environment variable values

Scenario: Flag overrides environment

  • WHEN --url or --token flags are provided
  • THEN the flag values SHALL take precedence over environment variables

Scenario: Missing configuration

  • WHEN neither the environment variable nor the flag is set for URL or token
  • THEN the system SHALL print an error message and exit with code 1

Requirement: Cursor-based pagination

The system SHALL implement cursor-based pagination for all list endpoints that support it. Paginated endpoints are: GET /boards/{boardId}/actions (beforeId), GET /cards/{cardId}/actions (beforeId), GET /lists/{listId}/cards (before), GET /cards/{cardId}/comments (beforeId). List methods SHALL accept a limit parameter. When limit is 0, the client SHALL fetch all pages. When limit is greater than 0, the client SHALL stop fetching after accumulating at least limit items and truncate the result to exactly limit items. Each page fetch SHALL be logged at DEBUG level.

Scenario: Fetch all pages

  • WHEN a list method is called with limit 0
  • THEN the client SHALL fetch pages until an empty page is returned
  • AND all items from all pages SHALL be returned

Scenario: Fetch with limit

  • WHEN a list method is called with limit N (N > 0)
  • THEN the client SHALL stop fetching after accumulating N or more items
  • AND the returned slice SHALL contain exactly N items

Scenario: Single page result

  • WHEN the API returns fewer items than a page size and no more pages exist
  • THEN the client SHALL return those items without making additional requests

Requirement: Project operations

The client SHALL provide methods to list all accessible projects (GET /projects) and get a single project by ID (GET /projects/{id}).

Scenario: List projects

  • WHEN ListProjects is called
  • THEN the client SHALL send GET /projects and return a slice of Project models

Scenario: Get project

  • WHEN GetProject is called with a project ID
  • THEN the client SHALL send GET /projects/{id} and return a Project model

Requirement: Board operations

The client SHALL provide a method to get a single board by ID (GET /boards/{id}) and list board actions (GET /boards/{boardId}/actions) with pagination support.

Scenario: Get board

  • WHEN GetBoard is called with a board ID
  • THEN the client SHALL send GET /boards/{id} and return a Board model including its included lists

Scenario: List board actions

  • WHEN ListBoardActions is called with a board ID and limit
  • THEN the client SHALL send paginated GET /boards/{boardId}/actions requests and return a slice of Action models

Requirement: Card CRUD operations

The client SHALL provide methods for: get card (GET /cards/{id}), create card (POST /lists/{listId}/cards), update card (PATCH /cards/{id}), delete card (DELETE /cards/{id}), and duplicate card (POST /cards/{id}/duplicate). The client SHALL provide a method to list cards in a list (GET /lists/{listId}/cards) with pagination support. The client SHALL provide a method to list card actions (GET /cards/{cardId}/actions) with pagination support.

Scenario: Get card

  • WHEN GetCard is called with a card ID
  • THEN the client SHALL send GET /cards/{id} and return a Card model

Scenario: Create card

  • WHEN CreateCard is called with a list ID and card fields (name, description, type, position, dueDate, isDueCompleted)
  • THEN the client SHALL send POST /lists/{listId}/cards with the provided fields and return the created Card

Scenario: Update card

  • WHEN UpdateCard is called with a card ID and update fields
  • THEN the client SHALL send PATCH /cards/{id} with only the provided fields and return the updated Card

Scenario: Delete card

  • WHEN DeleteCard is called with a card ID
  • THEN the client SHALL send DELETE /cards/{id}

Scenario: Duplicate card

  • WHEN DuplicateCard is called with a card ID, name, and position
  • THEN the client SHALL send POST /cards/{id}/duplicate and return the new Card

Scenario: List cards in list

  • WHEN ListCards is called with a list ID and limit
  • THEN the client SHALL send paginated GET /lists/{listId}/cards requests and return a slice of Card models

Scenario: List card actions

  • WHEN ListCardActions is called with a card ID and limit
  • THEN the client SHALL send paginated GET /cards/{cardId}/actions requests and return a slice of Action models

Requirement: Comment operations

The client SHALL provide methods for: list comments (GET /cards/{cardId}/comments) with pagination, create comment (POST /cards/{cardId}/comments), update comment (PATCH /comments/{id}), and delete comment (DELETE /comments/{id}).

Scenario: List comments

  • WHEN ListComments is called with a card ID and limit
  • THEN the client SHALL send paginated GET /cards/{cardId}/comments requests and return a slice of Comment models

Scenario: Create comment

  • WHEN CreateComment is called with a card ID and text
  • THEN the client SHALL send POST /cards/{cardId}/comments with the text and return the created Comment

Scenario: Update comment

  • WHEN UpdateComment is called with a comment ID and text
  • THEN the client SHALL send PATCH /comments/{id} and return the updated Comment

Scenario: Delete comment

  • WHEN DeleteComment is called with a comment ID
  • THEN the client SHALL send DELETE /comments/{id}

Requirement: Task list operations

The client SHALL provide methods for: create task list (POST /cards/{cardId}/task-lists), get task list (GET /task-lists/{id}), update task list (PATCH /task-lists/{id}), and delete task list (DELETE /task-lists/{id}).

Scenario: Create task list

  • WHEN CreateTaskList is called with a card ID and fields (name, position, showOnFrontOfCard, hideCompletedTasks)
  • THEN the client SHALL send POST /cards/{cardId}/task-lists and return the created TaskList

Scenario: Get task list

  • WHEN GetTaskList is called with a task list ID
  • THEN the client SHALL send GET /task-lists/{id} and return a TaskList model

Scenario: Update task list

  • WHEN UpdateTaskList is called with a task list ID and update fields
  • THEN the client SHALL send PATCH /task-lists/{id} and return the updated TaskList

Scenario: Delete task list

  • WHEN DeleteTaskList is called with a task list ID
  • THEN the client SHALL send DELETE /task-lists/{id}

Requirement: Task operations

The client SHALL provide methods for: create task (POST /task-lists/{taskListId}/tasks), update task (PATCH /tasks/{id}), and delete task (DELETE /tasks/{id}).

Scenario: Create task

  • WHEN CreateTask is called with a task list ID and fields (name, position, isCompleted, assigneeUserId, linkedCardId)
  • THEN the client SHALL send POST /task-lists/{taskListId}/tasks and return the created Task

Scenario: Update task

  • WHEN UpdateTask is called with a task ID and update fields (name, position, isCompleted, assigneeUserId, taskListId)
  • THEN the client SHALL send PATCH /tasks/{id} and return the updated Task

Scenario: Delete task

  • WHEN DeleteTask is called with a task ID
  • THEN the client SHALL send DELETE /tasks/{id}

Requirement: Label operations

The client SHALL provide methods for: create label (POST /boards/{boardId}/labels), update label (PATCH /labels/{id}), and delete label (DELETE /labels/{id}).

Scenario: Create label

  • WHEN CreateLabel is called with a board ID and fields (name, color, position)
  • THEN the client SHALL send POST /boards/{boardId}/labels and return the created Label

Scenario: Update label

  • WHEN UpdateLabel is called with a label ID and update fields (name, color, position)
  • THEN the client SHALL send PATCH /labels/{id} and return the updated Label

Scenario: Delete label

  • WHEN DeleteLabel is called with a label ID
  • THEN the client SHALL send DELETE /labels/{id}

Requirement: Card label operations

The client SHALL provide methods for: add label to card (POST /cards/{cardId}/card-labels) and remove label from card (DELETE /cards/{cardId}/card-labels/labelId:{labelId}).

Scenario: Add label to card

  • WHEN AddCardLabel is called with a card ID and label ID
  • THEN the client SHALL send POST /cards/{cardId}/card-labels with the label ID

Scenario: Remove label from card

  • WHEN RemoveCardLabel is called with a card ID and label ID
  • THEN the client SHALL send DELETE /cards/{cardId}/card-labels/labelId:{labelId}

Requirement: Card membership operations

The client SHALL provide methods for: assign user to card (POST /cards/{cardId}/card-memberships) and unassign user from card (DELETE /cards/{cardId}/card-memberships/userId:{userId}).

Scenario: Assign user to card

  • WHEN AddCardMember is called with a card ID and user ID
  • THEN the client SHALL send POST /cards/{cardId}/card-memberships with the user ID

Scenario: Unassign user from card

  • WHEN RemoveCardMember is called with a card ID and user ID
  • THEN the client SHALL send DELETE /cards/{cardId}/card-memberships/userId:{userId}