API: REST endpoint to move project cards between columns (PAT-compatible) #3

Closed
opened 2026-05-15 18:02:34 +03:00 by oleks · 1 comment
Owner

Problem

Gitea has no REST endpoint to move a project board card between columns. The only existing route is the web handler POST /{owner}/-/projects/{id}/move, which gates on browser session + CSRF token — calling it with a PAT returns HTTP 303 redirecting to /user/login, which is easy to mistake for a successful 3xx.

What works today (read + link)

  • PATCH /api/v1/repos/{owner}/{repo}/issues/{n} with {"projects":[<id>, ...]} — links an issue to a project (defaults the card to the project's default column).
  • GET /api/v1/users/{u}/projects/{id}/columns and .../columns/{col_id}/issues — landed in #1 / #2 for user and org scopes (parity with repo scope).

What's missing (write — column placement)

  • No POST /api/v1/projects/columns/{column_id}/move-issue (or equivalent) to place a card into a specific column.
  • No way to reorder cards within a column via REST.
  • The existing web /move route can't be called with a PAT.

Proposal

Add REST write endpoints for project board column operations, symmetric to the read endpoints already merged. Candidate surface:

  • POST /api/v1/projects/columns/{column_id}/issues — body: {issue_id, sorting?} — place card into column at position.
  • PATCH /api/v1/projects/columns/{column_id}/issues/{issue_id} — body: {target_column_id?, sorting?} — move card to another column or reorder.

Auth: same PAT scopes as the existing project read endpoints.

Related

  • oleks/gitea#1 (closed) — original report of column ops being web-only
  • oleks/gitea#2 (closed, merged) — added user/org-scope read endpoints
  • oleks/gitea-mcp#3 (closed, merged) — wired MCP tools to the new read endpoints; the move_issue write method is exposed but has no working underlying API to call
  • oleks/cluster#14 (open) — operational tracking issue with the trap (303 bounce)

Trap

An HTTP 303 from a Gitea web route called with a PAT is an auth-bounce to login, not success. Any new endpoint must return a real API response (200/204 with JSON), not a redirect.

## Problem Gitea has no REST endpoint to move a project board card between columns. The only existing route is the web handler `POST /{owner}/-/projects/{id}/move`, which gates on browser session + CSRF token — calling it with a PAT returns HTTP 303 redirecting to `/user/login`, which is easy to mistake for a successful 3xx. ## What works today (read + link) - `PATCH /api/v1/repos/{owner}/{repo}/issues/{n}` with `{"projects":[<id>, ...]}` — links an issue to a project (defaults the card to the project's default column). - `GET /api/v1/users/{u}/projects/{id}/columns` and `.../columns/{col_id}/issues` — landed in #1 / #2 for user and org scopes (parity with repo scope). ## What's missing (write — column placement) - No `POST /api/v1/projects/columns/{column_id}/move-issue` (or equivalent) to place a card into a specific column. - No way to reorder cards within a column via REST. - The existing web `/move` route can't be called with a PAT. ## Proposal Add REST write endpoints for project board column operations, symmetric to the read endpoints already merged. Candidate surface: - `POST /api/v1/projects/columns/{column_id}/issues` — body: `{issue_id, sorting?}` — place card into column at position. - `PATCH /api/v1/projects/columns/{column_id}/issues/{issue_id}` — body: `{target_column_id?, sorting?}` — move card to another column or reorder. Auth: same PAT scopes as the existing project read endpoints. ## Related - oleks/gitea#1 (closed) — original report of column ops being web-only - oleks/gitea#2 (closed, merged) — added user/org-scope read endpoints - oleks/gitea-mcp#3 (closed, merged) — wired MCP tools to the new read endpoints; the `move_issue` write method is exposed but has no working underlying API to call - oleks/cluster#14 (open) — operational tracking issue with the trap (303 bounce) ## Trap An HTTP 303 from a Gitea web route called with a PAT is an auth-bounce to login, not success. Any new endpoint must return a real API response (200/204 with JSON), not a redirect.
Author
Owner

Already shipped in #2 (merged). Both list-per-column and move-card-between-columns are PAT-compatible REST endpoints today:

List issues in a column

GET /api/v1/users/{username}/projects/{id}/columns/{column_id}/issues
GET /api/v1/orgs/{org}/projects/{id}/columns/{column_id}/issues

Move card between columns

POST /api/v1/users/{username}/projects/{id}/issues/{issue_id}/move
POST /api/v1/orgs/{org}/projects/{id}/issues/{issue_id}/move
Body: {"column_id": <target>, "sorting"?: <int>}
Returns: 204 No Content

Note: issue_id is the issue's internal id (top-level field on the issue object), not the per-repo index.

Smoke test against project 7 (2026-05-15)

$ curl -X POST -H "Authorization: token …" -H "Content-Type: application/json" \
    -d '{"column_id":27}' \
    https://git.oleks.space/api/v1/users/oleks/projects/7/issues/121/move
HTTP 204

Before: col 27 (High Priority) had 0 issues. After: 1 issue. Reverse move with {"column_id":25} → 204, state restored. List endpoint returned correct counts at every step.

Note on the proposed shape

The issue proposed a global, owner-less endpoint (POST /api/v1/projects/columns/{column_id}/issues). The shipped surface is owner-scoped (/users/{u}/... or /orgs/{org}/...) which mirrors the existing repo-scope conventions. If you want the global variant as a more ergonomic alternative, file a follow-up — but the missing functionality is no longer missing.

Closing as resolved.

Already shipped in #2 (merged). Both list-per-column and move-card-between-columns are PAT-compatible REST endpoints today: **List issues in a column** ``` GET /api/v1/users/{username}/projects/{id}/columns/{column_id}/issues GET /api/v1/orgs/{org}/projects/{id}/columns/{column_id}/issues ``` **Move card between columns** ``` POST /api/v1/users/{username}/projects/{id}/issues/{issue_id}/move POST /api/v1/orgs/{org}/projects/{id}/issues/{issue_id}/move Body: {"column_id": <target>, "sorting"?: <int>} Returns: 204 No Content ``` Note: `issue_id` is the issue's internal `id` (top-level field on the issue object), not the per-repo `index`. ### Smoke test against project 7 (2026-05-15) ``` $ curl -X POST -H "Authorization: token …" -H "Content-Type: application/json" \ -d '{"column_id":27}' \ https://git.oleks.space/api/v1/users/oleks/projects/7/issues/121/move HTTP 204 ``` Before: col 27 (High Priority) had 0 issues. After: 1 issue. Reverse move with `{"column_id":25}` → 204, state restored. List endpoint returned correct counts at every step. ### Note on the proposed shape The issue proposed a global, owner-less endpoint (`POST /api/v1/projects/columns/{column_id}/issues`). The shipped surface is owner-scoped (`/users/{u}/...` or `/orgs/{org}/...`) which mirrors the existing repo-scope conventions. If you want the global variant as a more ergonomic alternative, file a follow-up — but the missing functionality is no longer missing. Closing as resolved.
oleks closed this issue 2026-05-15 20:51:00 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: oleks/gitea#3