diff --git a/app/controllers/boards/columns_controller.rb b/app/controllers/boards/columns_controller.rb
index 1532d76dc..4f6770e28 100644
--- a/app/controllers/boards/columns_controller.rb
+++ b/app/controllers/boards/columns_controller.rb
@@ -3,6 +3,11 @@ class Boards::ColumnsController < ApplicationController
before_action :set_column, only: %i[ show update destroy ]
+ def index
+ @columns = @board.columns.sorted
+ fresh_when etag: @columns
+ end
+
def show
set_page_and_extract_portion_from @column.cards.active.latest.with_golden_first.preloaded
fresh_when etag: @page.records
diff --git a/app/controllers/cards/comments_controller.rb b/app/controllers/cards/comments_controller.rb
index c38181862..d353930ee 100644
--- a/app/controllers/cards/comments_controller.rb
+++ b/app/controllers/cards/comments_controller.rb
@@ -4,6 +4,10 @@ class Cards::CommentsController < ApplicationController
before_action :set_comment, only: %i[ show edit update destroy ]
before_action :ensure_creatorship, only: %i[ edit update destroy ]
+ def index
+ set_page_and_extract_portion_from @card.comments.chronologically
+ end
+
def create
@comment = @card.comments.create!(comment_params)
diff --git a/app/views/boards/columns/index.json.jbuilder b/app/views/boards/columns/index.json.jbuilder
new file mode 100644
index 000000000..4142996de
--- /dev/null
+++ b/app/views/boards/columns/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @columns, partial: "columns/column", as: :column
diff --git a/app/views/cards/comments/index.json.jbuilder b/app/views/cards/comments/index.json.jbuilder
new file mode 100644
index 000000000..7e261ccc7
--- /dev/null
+++ b/app/views/cards/comments/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @page.records, partial: "cards/comments/comment", as: :comment
diff --git a/app/views/users/_user.json.jbuilder b/app/views/users/_user.json.jbuilder
index 6a49bd1de..0884f225a 100644
--- a/app/views/users/_user.json.jbuilder
+++ b/app/views/users/_user.json.jbuilder
@@ -1,7 +1,7 @@
json.cache! user do
json.(user, :id, :name, :role, :active)
- json.email_address user.identity.email_address
+ json.email_address user.identity&.email_address
json.created_at user.created_at.utc
json.url user_url(user)
diff --git a/docs/API.md b/docs/API.md
index da347025d..8673cf37b 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -351,7 +351,7 @@ __Request:__
"name": "Updated board name",
"auto_postpone_period": 14,
"public_description": "This is a **public** description of the board.",
- all_access: false,
+ "all_access": false,
"user_ids": [
"03f5v9zppzlksuj4mxba2nbzn",
"03f5v9zjw7pz8717a4no1h8a7"
@@ -594,6 +594,37 @@ Returns `204 No Content` on success.
Comments are attached to cards and support rich text.
+#### `GET /:account_slug/cards/:card_number/comments`
+
+Returns a paginated list of comments on a card, sorted chronologically (oldest first).
+
+__Response:__
+
+```json
+[
+ {
+ "id": "03f5v9zo9qlcwwpyc0ascnikz",
+ "created_at": "2025-12-05T19:36:35.534Z",
+ "updated_at": "2025-12-05T19:36:35.534Z",
+ "body": {
+ "plain_text": "This looks great!",
+ "html": "
This looks great!
"
+ },
+ "creator": {
+ "id": "03f5v9zjw7pz8717a4no1h8a7",
+ "name": "David Heinemeier Hansson",
+ "role": "owner",
+ "active": true,
+ "email_address": "david@example.com",
+ "created_at": "2025-12-05T19:36:35.401Z",
+ "url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
+ },
+ "reactions_url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz/reactions",
+ "url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz"
+ }
+]
+```
+
#### `GET /:account_slug/cards/:card_number/comments/:comment_id`
Returns a specific comment.
@@ -679,6 +710,31 @@ Returns `204 No Content` on success.
Reactions are short - 16 character long - responses to comments.
+#### `GET /:account_slug/cards/:card_number/comments/:comment_id/reactions`
+
+Returns a list of reactions on a comment.
+
+__Response:__
+
+```json
+[
+ {
+ "id": "03f5v9zo9qlcwwpyc0ascnikz",
+ "content": "👍",
+ "reacter": {
+ "id": "03f5v9zjw7pz8717a4no1h8a7",
+ "name": "David Heinemeier Hansson",
+ "role": "owner",
+ "active": true,
+ "email_address": "david@example.com",
+ "created_at": "2025-12-05T19:36:35.401Z",
+ "url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
+ },
+ "url": "http://fizzy.localhost:3006/897362094/cards/3/comments/03f5v9zo9qlcwwpyc0ascnikz/reactions/03f5v9zo9qlcwwpyc0ascnikz"
+ }
+]
+```
+
#### `POST /:account_slug/cards/:card_number/comments/:comment_id/reactions`
Adds a reaction to a comment.
@@ -785,6 +841,29 @@ Returns `204 No Content` on success.
Columns represent stages in a workflow on a board. Cards move through columns as they progress.
+#### `GET /:account_slug/boards/:board_id/columns`
+
+Returns a list of columns on a board, sorted by position.
+
+__Response:__
+
+```json
+[
+ {
+ "id": "03f5v9zkft4hj9qq0lsn9ohcm",
+ "name": "Recording",
+ "color": "var(--color-card-default)",
+ "created_at": "2025-12-05T19:36:35.534Z"
+ },
+ {
+ "id": "03f5v9zkft4hj9qq0lsn9ohcn",
+ "name": "Published",
+ "color": "var(--color-card-4)",
+ "created_at": "2025-12-05T19:36:35.534Z"
+ }
+]
+```
+
#### `GET /:account_slug/boards/:board_id/columns/:column_id`
Returns the specified column.
@@ -1017,3 +1096,30 @@ Marks all unread notifications as read.
__Response:__
Returns `204 No Content` on success.
+
+### Tags
+
+Tags are labels that can be applied to cards for organization and filtering.
+
+#### `GET /:account_slug/tags`
+
+Returns a list of all tags in the account, sorted alphabetically.
+
+__Response:__
+
+```json
+[
+ {
+ "id": "03f5v9zo9qlcwwpyc0ascnikz",
+ "title": "bug",
+ "created_at": "2025-12-05T19:36:35.534Z",
+ "url": "http://fizzy.localhost:3006/897362094/cards?tag_ids[]=03f5v9zo9qlcwwpyc0ascnikz"
+ },
+ {
+ "id": "03f5v9zo9qlcwwpyc0ascnilz",
+ "title": "feature",
+ "created_at": "2025-12-05T19:36:35.534Z",
+ "url": "http://fizzy.localhost:3006/897362094/cards?tag_ids[]=03f5v9zo9qlcwwpyc0ascnilz"
+ }
+]
+```
diff --git a/test/controllers/boards/columns_controller_test.rb b/test/controllers/boards/columns_controller_test.rb
index 2bc96357c..d7542da27 100644
--- a/test/controllers/boards/columns_controller_test.rb
+++ b/test/controllers/boards/columns_controller_test.rb
@@ -37,6 +37,15 @@ class Boards::ColumnsControllerTest < ActionDispatch::IntegrationTest
end
end
+ test "index as JSON" do
+ board = boards(:writebook)
+
+ get board_columns_path(board), as: :json
+
+ assert_response :success
+ assert_equal board.columns.count, @response.parsed_body.count
+ end
+
test "show as JSON" do
column = columns(:writebook_in_progress)
diff --git a/test/controllers/cards/comments_controller_test.rb b/test/controllers/cards/comments_controller_test.rb
index 8fc0289be..138fada59 100644
--- a/test/controllers/cards/comments_controller_test.rb
+++ b/test/controllers/cards/comments_controller_test.rb
@@ -28,6 +28,15 @@ class Cards::CommentsControllerTest < ActionDispatch::IntegrationTest
assert_response :forbidden
end
+ test "index as JSON" do
+ card = cards(:logo)
+
+ get card_comments_path(card), as: :json
+
+ assert_response :success
+ assert_equal card.comments.count, @response.parsed_body.count
+ end
+
test "create as JSON" do
card = cards(:logo)