API Reference
API Reference
Base URL: https://shiftwise-0sin.onrender.com
Local: http://localhost:3001
Authentication
All protected routes require a Bearer token in the Authorization header:
Authorization: Bearer <accessToken>
Access tokens are short-lived (15 minutes). Use the refresh endpoint to get a new one without re-entering credentials. See Architecture for the full auth flow.
Endpoints
Auth
POST /api/auth/register
Register a new user and optionally create a workspace.
Body:
{
"email": "will.power@demo.com",
"password": "password123",
"name": "Will Power",
"workspaceName": "Demo Cafe"
}
Response 201:
{
"accessToken": "eyJ...",
"user": { "id": "...", "email": "will.power@demo.com", "name": "Will Power" }
}
POST /api/auth/login
Authenticate and receive tokens.
Body:
{
"email": "will.power@demo.com",
"password": "password123"
}
Response 200:
{
"accessToken": "eyJ...",
"user": { "id": "...", "email": "will.power@demo.com", "name": "Will Power" },
"workspace": { "id": "...", "name": "Demo Cafe", "role": "MANAGER" }
}
A refreshToken httpOnly cookie is also set automatically.
POST /api/auth/refresh
Exchange a refresh token cookie for a new access token. The refresh token is rotated on each use.
Requires: refreshToken httpOnly cookie (sent automatically by browser)
Response 200:
{
"accessToken": "eyJ..."
}
POST /api/auth/logout
Invalidate the refresh token and clear the cookie.
Response 200:
{ "ok": true }
Workspaces
GET /api/workspaces/:workspaceId/employees
List all members of a workspace.
Auth: Any workspace member
Response 200:
[
{
"id": "...",
"name": "Will Power",
"email": "will.power@demo.com",
"timezone": "Australia/Sydney",
"role": "MANAGER",
"joinedAt": "2026-01-01T00:00:00.000Z"
}
]
POST /api/workspaces/:workspaceId/employees
Add a new team member to the workspace. Creates a new user account if the email doesn’t exist yet. New users inherit the workspace timezone.
Auth: OWNER or MANAGER only
Body:
{
"email": "lou.poles@demo.com",
"name": "Lou Poles",
"role": "EMPLOYEE",
"password": "changeme123"
}
Response 201:
{
"id": "...",
"name": "Lou Poles",
"email": "lou.poles@demo.com",
"timezone": "Australia/Sydney",
"role": "EMPLOYEE",
"joinedAt": "2026-03-18T00:00:00.000Z"
}
DELETE /api/workspaces/:workspaceId/employees/:userId
Remove a member from the workspace. Cannot remove yourself.
Auth: OWNER or MANAGER only
Response 204: No content
Error responses
All errors follow a consistent format:
{
"error": "Human readable message",
"code": "MACHINE_READABLE_CODE"
}
In development, 500 errors also include a stack field.
| Code | Status | Description |
|---|---|---|
BAD_REQUEST |
400 | Invalid input or validation failure |
UNAUTHORIZED |
401 | Missing, invalid, or expired token |
FORBIDDEN |
403 | Authenticated but insufficient role |
NOT_FOUND |
404 | Resource does not exist |
CONFLICT |
409 | Resource already exists |
INTERNAL_ERROR |
500 | Unexpected server error |
Skills
GET /api/workspaces/:workspaceId/skills
List all skills defined in a workspace, ordered alphabetically.
Auth: Any workspace member
Response 200:
[{ "id": "...", "name": "Barista" }]
POST /api/workspaces/:workspaceId/skills
Create a new skill. Names must be unique within the workspace.
Auth: OWNER or MANAGER
Body: { "name": "Barista" }
Response 201: { "id": "...", "name": "Barista" }
DELETE /api/workspaces/:workspaceId/skills/:skillId
Delete a skill and remove it from all employees.
Auth: OWNER or MANAGER
Response 204: No content
Employee skills
GET /api/workspaces/:workspaceId/employees/:userId/skills
List skills assigned to a specific employee.
Auth: Any workspace member
Response 200: [{ "id": "...", "name": "Barista" }]
POST /api/workspaces/:workspaceId/employees/:userId/skills
Assign a workspace skill to an employee.
Auth: OWNER or MANAGER
Body: { "skillId": "..." }
Response 201: { "id": "...", "name": "Barista" }
DELETE /api/workspaces/:workspaceId/employees/:userId/skills/:skillId
Remove a skill from an employee.
Auth: OWNER or MANAGER
Response 204: No content
Shift templates
GET /api/workspaces/:workspaceId/shift-templates
List all shift templates, ordered by start time.
Auth: Any workspace member
Response 200:
[{ "id": "...", "name": "Morning", "startTime": "06:00", "endTime": "14:00" }]
POST /api/workspaces/:workspaceId/shift-templates
Create a shift template. Times must be in HH:mm format.
Auth: OWNER or MANAGER
Body: { "name": "Morning", "startTime": "06:00", "endTime": "14:00" }
Response 201: { "id": "...", "name": "Morning", "startTime": "06:00", "endTime": "14:00" }
DELETE /api/workspaces/:workspaceId/shift-templates/:templateId
Delete a shift template.
Auth: OWNER or MANAGER
Response 204: No content
Forecast
GET /api/workspaces/:workspaceId/forecast
List all forecast slots, ordered by day then time.
Auth: Any workspace member
Response 200:
[{ "id": "...", "dayOfWeek": 1, "time": "09:00", "required": 3 }]
dayOfWeek: 0 = Sunday … 6 = Saturday. time is the start of a 30-minute window.
PUT /api/workspaces/:workspaceId/forecast
Create or update the demand for a given day + time slot. Identified by the unique (workspaceId, dayOfWeek, time) key — sending the same day + time updates required.
Auth: OWNER or MANAGER
Body: { "dayOfWeek": 1, "time": "09:00", "required": 3 }
Response 200: { "id": "...", "dayOfWeek": 1, "time": "09:00", "required": 3 }
DELETE /api/workspaces/:workspaceId/forecast/:slotId
Delete a forecast slot.
Auth: OWNER or MANAGER
Response 204: No content
Availability
GET /api/workspaces/:workspaceId/employees/:userId/availability
List availability windows for an employee, ordered by day then start time.
Auth: Any workspace member
Response 200:
[{ "id": "...", "dayOfWeek": 1, "startTime": "07:00", "endTime": "15:00" }]
PUT /api/workspaces/:workspaceId/employees/:userId/availability
Create or update an availability window. Identified by (membershipId, dayOfWeek, startTime) — sending the same day + start time updates endTime.
Auth: OWNER or MANAGER
Body: { "dayOfWeek": 1, "startTime": "07:00", "endTime": "15:00" }
Response 200: { "id": "...", "dayOfWeek": 1, "startTime": "07:00", "endTime": "15:00" }
DELETE /api/workspaces/:workspaceId/employees/:userId/availability/:availabilityId
Delete an availability window.
Auth: OWNER or MANAGER
Response 204: No content
Health check
GET /api/health
{ "status": "ok", "timestamp": "2026-03-18T09:00:00.000Z" }