API Reference

REST API, Ticket API, webhooks, Prometheus, and Swagger UI


Interactive API (Swagger UI)

WarmDesk ships with a full Swagger UI at:

http://<your-server>:8080/swagger/index.html

The interactive documentation lists every endpoint, shows request/response schemas, and lets you try requests directly from the browser. Enter a valid JWT Bearer token via the Authorize button to authenticate requests.

Authentication

JWT (user sessions)

Obtain an access token:

curl -s -X POST https://warmdesk.example.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"hunter2"}' \
  | jq .access_token

Use the token in subsequent requests:

curl -H "Authorization: Bearer <token>" \
     https://warmdesk.example.com/api/v1/projects
  • Access tokens expire after 15 minutes

  • The frontend silently refreshes using the 7-day refresh token

  • A 401 response from the API signals an expired token

API keys

API keys authenticate CI/CD pipelines and automation scripts. Create a personal API key in Settings → API Keys (full access as you, across all your projects), or a project-scoped key in Project Settings → API Keys (locked to that one project).

Send the key in either header:

  • X-API-Key: <key>

  • Authorization: ApiKey <key> — for tools that can only set the Authorization header (e.g. Prometheus)

curl -H "X-API-Key: cwk_abc123..." \
     https://warmdesk.example.com/api/v1/projects

API keys carry the same project permissions as the user who created them, and work on all authenticated endpoints, not just the Ticket API. A project-scoped key only works on routes that contain its own project’s slug (e.g. /api/v1/projects/{slug}/…​ or /api/v1/ticket/{slug}/…​); anything else returns key is scoped to a specific project. A card transfer (…​/cards/{cardId}/transfer) additionally needs the key to be valid for the target project, so a project key can never write outside its own project; transferring a card needs a personal key. Keys with the backup global role can trigger backups without a full admin account.

Ticket API

The Ticket API is designed for CI/CD automation and external tools. All endpoints live under /api/v1/ticket/{projectSlug}/ and use X-API-Key authentication. The GET endpoints need only viewer access and never change anything; the others need member access (commenting needs viewer access).

MethodPathAction

GET

/api/v1/ticket/{slug}/columns

List columns (lanes), left to right

GET

/api/v1/ticket/{slug}/cards

List cards

GET

/api/v1/ticket/{slug}/cards/{cardId}

Get one card, with its comments

POST

/api/v1/ticket/{slug}/cards

Create a card

POST

/api/v1/ticket/{slug}/cards/{cardId}/comments

Add a comment

PATCH

/api/v1/ticket/{slug}/cards/{cardId}

Update a card’s fields (partial)

PATCH

/api/v1/ticket/{slug}/cards/{cardId}/move

Move a card to another column

POST

/api/v1/ticket/{slug}/cards/{cardId}/transfer

Move or copy a card to another project

Addressing cards and lanes: {cardId} is either the numeric id or the card’s key (ANSI-12, prefix case-insensitive); a card from another project always returns 404. A card that moved to another project keeps answering to its old key in its new project; asking for it in the old project returns 404 with moved_to (the new key) and project (the new slug), when the key’s user can see that project. Wherever a lane is expected (create, move), send column_id (number) or column (lane name, case-insensitive), not both.

Read a lane

GET /cards returns open cards ordered by column, then position. Each card includes column_name and its key (e.g. ANSI-12), plus priority, story_points, time_spent_minutes, due_date, labels, tags, assignees, and epic.

Query parameterTypeNotes

column

string

Only cards in the column with this name (case-insensitive), e.g. Backlog

column_id

number

Only cards in this column

include_closed

bool

true to also return closed cards

An unknown column returns 400 column not found in project.

curl -s -H "X-API-Key: cwk_abc123..." \
  "https://warmdesk.example.com/api/v1/ticket/myproject/cards?column=Backlog" \
  | jq '.[] | {key, title, priority, story_points}'

Create a card

title and a lane (column_id or column) are required. Optionally set description, priority (default none), start_date, due_date, and story_points, with the same rules as the update endpoint below. Invalid values return 400 and nothing is created; unrecognised fields are ignored, so existing callers keep working.

curl -X POST \
  -H "X-API-Key: cwk_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Security scan found CVE-2026-1234",
    "description": "**Severity:** High\n\nFound in dependency `foo@1.2.3`.",
    "column": "Backlog",
    "priority": "high"
  }' \
  "https://warmdesk.example.com/api/v1/ticket/myproject/cards"

Returns 201 Created with the new card, including its key.

Update a card

PATCH /cards/{cardId} is a partial update: only fields present in the body change, and a field you leave out keeps its value. Lane and position go through /move.

FieldTypeNotes

title

string

Must not be empty

description

string

Markdown

priority

string

One of none, low, medium, high, critical

closed

boolean

true closes the card, false reopens it

start_date

string or null

YYYY-MM-DD or RFC 3339; null clears it

due_date

string or null

Same; must not be before start_date

story_points

integer or null

Non-negative; null clears it

Dates are returned as RFC 3339 timestamps (a plain date is stored as midnight UTC). Any invalid or unknown field returns 400 and changes nothing; a card from another project returns 404; less than member access returns 403. The response is the updated card in the same shape as GET /cards/{cardId}, the change is logged in the card’s activity history, and open boards update live.

curl -X PATCH \
  -H "X-API-Key: cwk_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"priority": "medium", "start_date": "2026-09-24"}' \
  "https://warmdesk.example.com/api/v1/ticket/myproject/cards/ANSI-4"

Add a comment / move a card

# Add a comment
curl -X POST \
  -H "X-API-Key: cwk_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"body": "Deployment to production succeeded. Build #4242."}' \
  "https://warmdesk.example.com/api/v1/ticket/myproject/cards/MYPROJ-42/comments"

# Move the card to the Done lane (omit position to append at the end)
curl -X PATCH \
  -H "X-API-Key: cwk_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"column": "Done"}' \
  "https://warmdesk.example.com/api/v1/ticket/myproject/cards/MYPROJ-42/move"

# Close it
curl -X PATCH \
  -H "X-API-Key: cwk_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"closed": true}' \
  "https://warmdesk.example.com/api/v1/ticket/myproject/cards/MYPROJ-42"

Move a card to another project

curl -X POST \
  -H "X-API-Key: cwk_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"target_project": "operations", "column": "Backlog"}' \
  "https://warmdesk.example.com/api/v1/ticket/myproject/cards/MYPROJ-42/transfer"

Body fields: target_project (slug, required), column_id or column (lane in the target project, required), action (move, the default, or copy) and sub_cards (move, the default, or detach). The response (201) is the card in the target project; key is its new key.

A move keeps the card itself, so its comments, checklist, attachments, history, links and time spent come along. What changes:

  • The card gets the next number in the target project (MYPROJ-42 becomes e.g. OPS-7). The old key keeps resolving wherever a key is accepted: the Ticket API, git webhooks, card references and ticket links. The numeric card id stays the same, but paths are per project, so use the target project’s slug after the move; paths under the old project return 404.

  • Labels are matched by name onto the target project’s labels; missing ones are created there with the same colour.

  • Epic and sprint are cleared, as both belong to the source project.

  • Assignees and watchers who cannot see the target project are removed.

  • Sub-cards that move along land in the target lane with the same name as their current lane, or else in the requested lane.

A copy creates a new card with the title, description, priority, due date and tags. Sessions and personal keys can also use POST /api/v1/projects/{slug}/cards/{cardId}/transfer with target_project_slug, column_id, action and sub_cards.

Time-tracking timer

One running timer per user, kept on the server; stopping it books the time as ordinary time entries. The warmdesk-timer command-line tool uses these endpoints. They need time tracking enabled for the user and a session or a personal API key (project-scoped keys are refused).

MethodPathAction

GET

/api/v1/timer

Running timer, or {"running": false}

GET

/api/v1/timer/targets

Projects and customers you can start a timer on

POST

/api/v1/timer/start

Start (project_id and/or customer_id, optional description, time_zone); a running timer is booked first

POST

/api/v1/timer/stop

Stop and book; optional end (RFC 3339) for a forgotten timer

DELETE

/api/v1/timer

Discard without booking

The duration is rounded up to whole 15 minutes and split at midnight into one entry per day, in the timer’s time zone (default: the user’s setting).

Webhooks

Generic webhook (chat notifications)

Post any JSON payload to push a message to a project’s chat channel:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"text":"Deployment to staging succeeded 🚀","username":"CI"}' \
  "https://warmdesk.example.com/api/v1/webhooks/<token>"

The token is generated in Project Settings → Webhooks.

Git platform webhooks

Register an endpoint in your git host’s project settings to link commits and pull requests to cards. Card references in commit messages and PR titles (PROJ-42) are parsed automatically.

POST /api/v1/github-webhook/:token    # GitHub push, PR, issue events
POST /api/v1/gitlab-webhook/:token    # GitLab push, MR, issue events
POST /api/v1/gitea-webhook/:token     # Gitea / Forgejo push, PR, issue events

Response format

All success responses return JSON. All error responses use a consistent shape:

{ "error": "human readable message" }

HTTP status codes follow standard conventions:

200

OK — resource returned or updated

201

Created — resource created; id in response

204

No Content — successful delete

400

Bad Request — validation error

401

Unauthorized — missing or expired token

403

Forbidden — insufficient role

404

Not Found — resource does not exist or is not accessible

422

Unprocessable Entity — semantic validation error

500

Internal Server Error — server-side bug

WebSocket

Project WebSocket

wss://warmdesk.example.com/api/v1/ws/:projectSlug?token=<jwt>

Messages are JSON objects {"type": "…​", "payload": {…​}}.

Common event types:

board.card_created

A card was created in the project

board.card_updated

A card field was changed

board.card_moved

A card was moved to another column

board.card_deleted

A card was soft-deleted

chat.message

A new chat message was posted

topic.created

A new topic was created

presence.join

A user opened the project

presence.leave

A user left the project

User WebSocket

wss://warmdesk.example.com/api/v1/ws/user?token=<jwt>

Delivers user-level events: DM messages, @mentions, and card notifications. timer.changed ({"running": bool, "booked": n}) is sent whenever the user’s time-tracking timer is started, stopped, or discarded from any client; booked is the number of time entries the change created.

Prometheus metrics

GET /api/v1/metrics

Requires Authorization: Bearer <token> with the metrics or admin global role. Returns standard Prometheus text format.