Skip to content

Latest commit

 

History

History
691 lines (480 loc) · 14.9 KB

File metadata and controls

691 lines (480 loc) · 14.9 KB

API Reference

Base URL: http://localhost:7244

All endpoints return JSON: { "data": T } or { "error": "string" }.

Authentication: session cookie (thask_session) or API key (Authorization: Bearer <key>). All endpoints except login/register require authentication.

Backend: Go (Echo v4). Request validation via struct tags.


Authentication

POST /api/auth/register

Create a new account.

// Request
{ "email": "user@example.com", "password": "min8chars", "displayName": "User" }

// Response 201
{ "data": { "id": "uuid", "email": "user@example.com", "displayName": "User" } }

POST /api/auth/login

Logs in and sets session cookie. Performs session rotation — deletes all previous sessions for the user.

// Request
{ "email": "user@example.com", "password": "..." }

// Response 200
{ "data": { "id": "uuid", "email": "user@example.com", "displayName": "User" } }

GET /api/auth/me

Returns the current authenticated user.

// Response 200
{ "data": { "id": "uuid", "email": "user@example.com", "displayName": "User" } }

POST /api/auth/logout

Invalidates the session and clears the cookie.

// Response 200
{ "data": { "success": true } }

Authorization & Roles

Teams use role-based access control with four levels:

Role Level Capabilities
owner 4 Full control: delete team, transfer ownership
admin 3 Manage members, invite, update team settings
member 2 Create projects, edit nodes/edges, batch operations
viewer 1 Read-only access to projects and graphs

Middleware chain: AuthTeamAccess (resolves slug, verifies membership, sets role) → RequireRole(minRole).


API Keys

POST /api/auth/api-keys

Create an API key. Maximum 10 keys per user.

// Request
{ "name": "CLI Token", "expiresIn": 90 }

// Response 201
{
  "data": {
    "id": "uuid",
    "name": "CLI Token",
    "keyPrefix": "thsk_ab12345",
    "key": "thsk_ab1234567890...",
    "expiresAt": "2025-06-01T00:00:00Z",
    "createdAt": "2025-03-01T00:00:00Z"
  }
}

Note: The key field is only returned on creation. Store it securely.

Field Validation
name Required, 1-100 chars
expiresIn Optional, 1-365 days. Omit for no expiration.

GET /api/auth/api-keys

List your API keys (key hash is never returned).

// Response 200
{ "data": [{ "id": "uuid", "name": "CLI Token", "keyPrefix": "thsk_ab12345", "lastUsedAt": null, "expiresAt": "...", "createdAt": "..." }] }

DELETE /api/auth/api-keys/:keyId

Delete an API key.

// Response 200
{ "data": { "success": true } }

Teams

GET /api/teams

List all teams the user is a member of, with their projects.

// Response 200
{ "data": [{ "id": "uuid", "name": "Team", "slug": "team", "projects": [...] }] }

POST /api/teams

Create a new team. The creator becomes owner.

// Request
{ "name": "My Team", "slug": "my-team" }

// Response 201
{ "data": { "id": "uuid", "name": "My Team", "slug": "my-team", ... } }

GET /api/teams/:slug

Get a team by slug.

// Response 200
{ "data": { "id": "uuid", "name": "My Team", "slug": "my-team", ... } }

PATCH /api/teams/:slug

Update team name. Requires admin role or above.

// Request
{ "name": "New Team Name" }

// Response 200
{ "data": { "id": "uuid", "name": "New Team Name", "slug": "my-team", ... } }

DELETE /api/teams/:slug

Delete a team. Requires owner role.

// Response 200
{ "data": { "success": true } }

GET /api/teams/:slug/members

List team members. Requires team membership (authorization enforced).

// Response 200
{ "data": [{ "id": "uuid", "userId": "uuid", "role": "owner", "email": "...", "displayName": "..." }] }

POST /api/teams/:slug/members

Invite a user by email. Requires admin role or above. Admins cannot invite as admin or owner.

// Request
{ "email": "invite@example.com", "role": "member" }

// Response 201
{ "data": { "success": true } }
Field Validation
email Required, valid email
role Optional: admin, member, viewer. Default: member

PATCH /api/teams/:slug/members/:userId

Change a member's role. Requires admin role or above. Admins cannot manage other admins.

// Request
{ "role": "admin" }

// Response 200
{ "data": { "success": true } }

DELETE /api/teams/:slug/members/:userId

Remove a member. Requires admin role or above. Cannot remove owner.

// Response 200
{ "data": { "success": true } }

POST /api/teams/:slug/leave

Leave a team. Any member can leave. Sole owner must transfer ownership first.

// Response 200
{ "data": { "success": true } }

POST /api/teams/:slug/transfer

Transfer team ownership. Requires owner role. Target must be a team member.

// Request
{ "userId": "uuid" }

// Response 200
{ "data": { "success": true } }

GET /api/teams/:slug/projects

List projects in a team.

// Response 200
{ "data": [{ "id": "uuid", "name": "Project", ... }] }

POST /api/teams/:slug/projects

Create a project in a team.

// Request
{ "name": "New Project", "description": "optional" }

Projects

GET /api/projects/:projectId

Get project details. Requires team membership (any role).

// Response 200
{ "data": { "id": "uuid", "teamId": "uuid", "name": "Project", "description": "...", ... } }

PATCH /api/projects/:projectId

Update project. Requires member role.

// Request (all fields optional)
{ "name": "Updated Name", "description": "Updated desc" }

DELETE /api/projects/:projectId

Delete project. Requires member role.

// Response 200
{ "data": { "success": true } }

Nodes

All node endpoints require project access (verified via ProjectAccess middleware).

GET /api/projects/:projectId/nodes

Query params: ?type=TASK&status=PASS (optional filters)

// Response 200
{ "data": [{ "id": "uuid", "type": "TASK", "title": "...", "status": "IN_PROGRESS", ... }] }

POST /api/projects/:projectId/nodes

// Request
{
  "type": "TASK",
  "title": "New Node",
  "description": "optional",
  "status": "IN_PROGRESS",
  "positionX": 100,
  "positionY": 200
}

// Response 201
{ "data": { "id": "uuid", ... } }

GET /api/projects/:projectId/nodes/:nodeId

Returns node with connected edges, connected node IDs, and history.

// Response 200
{
  "data": {
    "id": "uuid", "type": "TASK", "title": "...",
    "connectedEdges": [...],
    "connectedNodeIds": ["uuid", ...],
    "history": [{ "id": "uuid", "action": "updated", "fieldName": "title", ... }]
  }
}

PATCH /api/projects/:projectId/nodes/:nodeId

Updates a node. Records history for each changed field. Triggers waterfall status propagation when status changes.

// Request (all fields optional)
{
  "title": "Updated",
  "status": "PASS",
  "type": "BUG",
  "description": "...",
  "assigneeId": "uuid",
  "tags": ["tag1", "tag2"],
  "parentId": "group-uuid | null"
}

DELETE /api/projects/:projectId/nodes/:nodeId

Deletes the node. If it's a GROUP, children are unparented (preserved).

// Response 200
{ "data": { "success": true } }

PATCH /api/projects/:projectId/nodes/positions

Batch update node positions (after drag or layout).

// Request
{
  "positions": [
    { "id": "uuid", "x": 100, "y": 200, "width": 300, "height": 200 },
    { "id": "uuid", "x": 400, "y": 100 }
  ]
}

POST /api/projects/:projectId/nodes/batch-delete

Delete multiple nodes. Requires member role.

// Request
{ "ids": ["uuid1", "uuid2"] }

// Response 200
{ "data": { "success": true } }

PATCH /api/projects/:projectId/nodes/batch-status

Batch update status for multiple nodes. Requires member role.

// Request
{ "ids": ["uuid1", "uuid2"], "status": "PASS" }

// Response 200
{ "data": [...] }

Edges

GET /api/projects/:projectId/edges

// Response 200
{ "data": [{ "id": "uuid", "sourceId": "uuid", "targetId": "uuid", "edgeType": "depends_on", "label": "" }] }

POST /api/projects/:projectId/edges

// Request
{ "sourceId": "uuid", "targetId": "uuid", "edgeType": "depends_on", "label": "optional" }

Constraints: no self-loops (validated server-side).

PATCH /api/projects/:projectId/edges/:edgeId

// Request
{ "edgeType": "blocks", "label": "updated label" }

DELETE /api/projects/:projectId/edges/:edgeId

// Response 200
{ "data": { "success": true } }

Project Sharing

GET /api/projects/:projectId/sharing

Get sharing settings and member list. Requires admin role.

// Response 200
{
  "data": {
    "linkSharing": "viewer",
    "shareUrl": "/shared/abc123...",
    "members": [
      { "id": "uuid", "projectId": "uuid", "userId": "uuid", "role": "editor", "createdAt": "...", "user": { "id": "uuid", "email": "...", "displayName": "..." } }
    ]
  }
}

PUT /api/projects/:projectId/sharing

Enable or disable link sharing. Requires admin role. Disabling clears the share token; re-enabling generates a new one (old links become invalid).

// Request
{ "linkSharing": "viewer" }

// Response 200
{ "data": { "linkSharing": "viewer", "shareUrl": "/shared/abc123..." } }
Value Description
off Sharing disabled (default)
viewer Anyone with the link can view
editor Anyone with the link can edit

POST /api/projects/:projectId/sharing/members

Invite a user to the project. Requires admin role.

// Request
{ "email": "user@example.com", "role": "editor" }

// Response 201
{ "data": { "success": true } }
Field Validation
email Required, valid email
role Required: editor or viewer

PATCH /api/projects/:projectId/sharing/members/:userId

Update a project member's role. Requires admin role.

// Request
{ "role": "viewer" }

// Response 200
{ "data": { "success": true } }

DELETE /api/projects/:projectId/sharing/members/:userId

Remove a project member. Requires admin role.

// Response 200
{ "data": { "success": true } }

Shared Access (Public)

Public endpoints for accessing shared projects. No authentication required. Rate limited to 5 requests/second.

GET /api/shared/:shareToken

Get shared project info (limited fields).

// Response 200
{ "data": { "id": "uuid", "name": "Project", "description": "...", "linkSharing": "viewer" } }

GET /api/shared/:shareToken/graph

Get full graph (nodes + edges) for a shared project.

GET /api/shared/:shareToken/events

SSE stream for realtime updates on a shared project. Same event types as authenticated SSE.

Write endpoints (editor mode only)

When linkSharing is editor, these endpoints are available:

Method Path Description
POST /api/shared/:shareToken/nodes Create node
PATCH /api/shared/:shareToken/nodes/:nodeId Update node
DELETE /api/shared/:shareToken/nodes/:nodeId Delete node
PATCH /api/shared/:shareToken/nodes/positions Batch update positions
POST /api/shared/:shareToken/edges Create edge
PATCH /api/shared/:shareToken/edges/:edgeId Update edge
DELETE /api/shared/:shareToken/edges/:edgeId Delete edge

Request/response formats are identical to the authenticated project endpoints.


Realtime Events (SSE)

GET /api/projects/:projectId/events

Server-Sent Events stream for realtime updates. Returns text/event-stream.

Event: connected        — initial connection confirmation
Event: node.created     — a node was created
Event: node.updated     — a node was updated
Event: node.deleted     — a node was deleted
Event: edge.created     — an edge was created
Event: edge.updated     — an edge was updated
Event: edge.deleted     — an edge was deleted
Event: graph.layout     — auto-layout was applied
Event: graph.import     — graph was imported

Each event payload:

{ "type": "node.updated", "projectId": "uuid", "data": {...}, "userId": "uuid" }

Graph Layout

POST /api/projects/:projectId/graph/layout

Run server-side auto-layout. Repositions all nodes and auto-sizes GROUPs. Requires member role.

// Request (all fields optional)
{ "algorithm": "dagre" }

// Response 200
{ "data": [{ "id": "uuid", "x": 100, "y": 200, "width": null, "height": null }, ...] }
Param Default Options Description
algorithm dagre dagre, grid Layout algorithm

Impact Analysis

GET /api/projects/:projectId/impact

Query params: ?since=2025-01-01T00:00:00Z&depth=2

Finds changed nodes and their downstream dependencies via bidirectional BFS.

// Response 200
{
  "data": {
    "changedNodes": [...],
    "impactedNodes": [...],
    "failNodes": [...],
    "impactEdges": [...]
  }
}
Param Default Description
since 7 days ago ISO date — nodes updated after this time
depth 2 BFS depth for downstream search

Summary

GET /api/projects/summary

Returns project and team counts for the authenticated user.

// Response 200
{ "data": { "teamCount": 3, "projectCount": 7 } }

Middleware

Middleware Scope Description
CORS Global Allows frontend origin with credentials
Rate Limiter Global 20 requests/second per client
Logger Global Structured logging via slog
Auth Protected routes Cookie or API key → user context
TeamAccess /api/teams/:teamSlug/* Resolves slug, verifies membership, sets team role
ProjectAccess /api/projects/:projectId/* Verifies team membership for the project
RequireRole Specific routes Enforces minimum role (owner/admin/member)
SharedAccess /api/shared/:shareToken/* Token validation, role mapping, 30s cache, anonymous context

Error Responses

// 400 Bad Request
{ "error": "Title is required" }

// 401 Unauthorized
{ "error": "Authentication required" }

// 404 Not Found
{ "error": "Node not found" }

// 500 Internal Server Error
{ "error": "Internal server error" }

Validation

All inputs are validated with Go struct tags (validate):

Endpoint Validated Fields
Register email (required, email), password (min 8), displayName (required)
Login email (required), password (required)
Create Team name (required), slug (required, alphanum+hyphen)
Invite Member email (required, email), role (oneof: owner/admin/member/viewer)
Create Project name (required)
Create Node type (required), title (required)
Update Node all fields optional, validated when present
Batch Positions positions array with id, x, y required
Create Edge sourceId (required), targetId (required)
Update Edge edgeType and label optional