# Productier AI Agent Skill This skill enables any AI agent (OpenClaw, Claude, GPT, Cursor, etc.) to interact with Productier - a calm, lightweight productivity workspace. ## Overview Productier combines calendar planning, kanban task management, notes, focus sessions, and mail-based task capture in a single workspace. **Core Features:** - Tasks with kanban board, labels, attachments, due dates - Calendar with month/week/day views and event management - Notes with markdown support - Focus sessions (Pomodoro-style) - Mail integration (IMAP/SMTP) with task creation from emails - Workspace collaboration with roles (owner/admin/member) --- ## Authentication **IMPORTANT:** All API operations require user authorization. The user must provide their session credentials before any operations can be performed. ### Auth Service Productier uses Better Auth with magic link (passwordless) or email/password authentication. **Auth Service URL:** - Development: `http://localhost:43001` - Production: Same domain at `/api/auth/*` ### Session-Based Authentication After login, sessions are stored in cookies. Include cookies in all API requests. **Login Methods:** 1. **Magic Link (Recommended):** ```http POST /api/auth/sign-in/magic-link Content-Type: application/json { "email": "user@example.com" } ``` User receives email with magic link. In development, check `/api/dev-mailbox` for the link. 2. **Email/Password:** ```http POST /api/auth/sign-in/email Content-Type: application/json { "email": "user@example.com", "password": "user-password" } ``` **Verify Session:** ```http GET /api/auth/session ``` Returns user object if authenticated. **Logout:** ```http POST /api/auth/sign-out ``` ### Authorization Header (Alternative) For programmatic access, the user may provide a session token: ```http Authorization: Bearer ``` --- ## API Reference **Base URL:** - Development: `http://localhost:48080/v1` - Production: `/v1/*` All endpoints (except public ones) require authentication via session cookies. ### Response Format **Success:** ```json { "data": { ... } } ``` **Error:** ```json { "error": { "code": "ERROR_CODE", "message": "Human-readable message", "requestId": "req-xxx" } } ``` --- ## Workspaces Workspaces are the top-level container. Users can be members of multiple workspaces. ### List Workspaces ```http GET /v1/workspaces ``` **Response:** ```json { "data": [ { "id": "uuid", "slug": "my-workspace", "name": "My Workspace", "role": "owner", "createdAt": "2024-01-01T00:00:00Z" } ] } ``` ### Workspace Context Most operations require a `workspaceSlug` query parameter to identify the workspace context. --- ## Tasks ### List Tasks ```http GET /v1/tasks?workspaceSlug={slug} ``` ### Create Task ```http POST /v1/tasks Content-Type: application/json { "workspaceSlug": "my-workspace", "title": "Task title", "description": "Markdown description", "boardGroupId": "group-uuid", "status": "todo", "color": "#3b82f6", "dueAt": "2024-01-15T10:00:00Z", "labelIds": ["label-uuid-1", "label-uuid-2"] } ``` **Task Status Values:** `todo`, `in-progress`, `done` ### Update Task ```http PATCH /v1/tasks/{taskId} Content-Type: application/json { "title": "Updated title", "status": "done", "dueAt": "2024-01-20T10:00:00Z" } ``` ### Task Attachments **Upload Attachment:** ```http POST /v1/tasks/{taskId}/attachments Content-Type: multipart/form-data file: ``` Max size: 20MB **Download Attachment:** ```http GET /v1/tasks/{taskId}/attachments/{attachmentId}/download ``` **Delete Attachment:** ```http DELETE /v1/tasks/{taskId}/attachments/{attachmentId} ``` --- ## Board Groups (Kanban Columns) ### List Board Groups ```http GET /v1/board-groups?workspaceSlug={slug} ``` ### Create Board Group ```http POST /v1/board-groups Content-Type: application/json { "workspaceSlug": "my-workspace", "name": "In Review", "color": "#f59e0b", "sortOrder": 3 } ``` ### Update Board Group ```http PATCH /v1/board-groups/{groupId} Content-Type: application/json { "name": "New Name", "color": "#10b981", "sortOrder": 2 } ``` --- ## Calendar Events ### List Events ```http GET /v1/calendar/events?workspaceSlug={slug} ``` ### Create Event ```http POST /v1/calendar/events Content-Type: application/json { "workspaceSlug": "my-workspace", "title": "Meeting", "description": "Weekly sync", "startsAt": "2024-01-15T10:00:00Z", "endsAt": "2024-01-15T11:00:00Z", "color": "#3b82f6", "linkedTaskId": "task-uuid" } ``` ### Update Event ```http PATCH /v1/calendar/events/{eventId} Content-Type: application/json { "title": "Updated Meeting", "startsAt": "2024-01-15T14:00:00Z", "endsAt": "2024-01-15T15:00:00Z" } ``` --- ## Notes ### List Notes ```http GET /v1/notes?workspaceSlug={slug} ``` ### Create Note ```http POST /v1/notes Content-Type: application/json { "workspaceSlug": "my-workspace", "title": "Meeting Notes", "content": "# Heading\n\nMarkdown content here" } ``` ### Update Note ```http PATCH /v1/notes/{noteId} Content-Type: application/json { "title": "Updated Title", "content": "Updated markdown content" } ``` --- ## Focus Sessions ### List Sessions ```http GET /v1/focus/sessions?workspaceSlug={slug} ``` ### Create Session ```http POST /v1/focus/sessions Content-Type: application/json { "workspaceSlug": "my-workspace", "taskId": "task-uuid", "mode": "pomodoro", "durationSeconds": 1500 } ``` ### Update Session (Complete/Pause) ```http PATCH /v1/focus/sessions/{sessionId} Content-Type: application/json { "completedAt": "2024-01-15T11:00:00Z", "pausedAt": null, "pausedTotalSeconds": 120 } ``` --- ## Labels ### List Labels ```http GET /v1/labels?workspaceSlug={slug} ``` ### Create Label ```http POST /v1/labels Content-Type: application/json { "workspaceSlug": "my-workspace", "name": "Priority", "color": "#ef4444" } ``` --- ## Mail Integration ### List Mailboxes ```http GET /v1/mailboxes?workspaceSlug={slug} ``` ### Connect Mailbox ```http POST /v1/mailboxes Content-Type: application/json { "workspaceSlug": "my-workspace", "label": "Work Email", "email": "user@example.com", "displayName": "John Doe", "imapHost": "imap.example.com", "imapPort": 993, "imapUsername": "user@example.com", "imapPassword": "password", "imapUseTls": true, "smtpHost": "smtp.example.com", "smtpPort": 587, "smtpUsername": "user@example.com", "smtpPassword": "password", "smtpUseTls": true } ``` ### Sync Mailbox ```http POST /v1/mailboxes/{mailboxId}/sync ``` ### List Mail Messages ```http GET /v1/mail/messages?workspaceSlug={slug}&mailboxId={mailboxId} ``` ### List Outgoing Mails ```http GET /v1/mail/outgoing?workspaceSlug={slug}&mailboxId={mailboxId} ``` ### Create Task from Mail ```http POST /v1/mail/messages/{messageId}/create-task Content-Type: application/json { "boardGroupId": "group-uuid", "title": "Task from email", "dueAt": "2024-01-20T10:00:00Z", "color": "#3b82f6" } ``` ### Send/Schedule Outgoing Mail ```http POST /v1/mail/outgoing Content-Type: application/json { "workspaceSlug": "my-workspace", "mailboxId": "mailbox-uuid", "to": [{"name": "Jane", "email": "jane@example.com"}], "cc": [], "bcc": [], "subject": "Hello", "textBody": "Email content", "htmlBody": "

Email content

", "scheduledFor": "2024-01-15T09:00:00Z" } ``` --- ## Activity Feed ### List Activity ```http GET /v1/activity?workspaceSlug={slug}&limit=40&type=task&q=search ``` **Query Parameters:** - `workspaceSlug` (required) - Workspace identifier - `limit` - Number of entries (default: 40) - `type` - Filter by type: `task`, `calendar`, `note`, `focus`, `mail`, `invite`, `system` - `q` - Search query --- ## Members & Invites ### List Members ```http GET /v1/members?workspaceSlug={slug} ``` ### Update Member Role ```http PATCH /v1/members/{memberId} Content-Type: application/json { "role": "admin", "status": "active" } ``` **Roles:** `owner`, `admin`, `member` **Status:** `active`, `inactive` ### List Invites ```http GET /v1/invites?workspaceSlug={slug} ``` ### Create Invite ```http POST /v1/invites Content-Type: application/json { "workspaceSlug": "my-workspace", "email": "newuser@example.com", "role": "member" } ``` ### Accept Invite (Requires Auth) ```http POST /v1/invites/{token}/accept Content-Type: application/json { "name": "New User" } ``` ### Revoke Invite ```http POST /v1/invites/{token}/revoke ``` ### Get Invite Details (Public - No Auth Required) ```http GET /v1/invites/{token} ``` --- ## Health & Metrics ### Health Check (Public) ```http GET /v1/health ``` Returns service status and storage health. **Response:** ```json { "ok": true, "mode": "production", "timestamp": "2024-01-15T10:00:00Z", "storage": { "provider": "s3", "ok": true } } ``` ### Metrics (Requires Auth Token) ```http GET /v1/metrics Authorization: Bearer {METRICS_AUTH_TOKEN} ``` ### Prometheus Metrics ```http GET /v1/metrics/prometheus Authorization: Bearer {METRICS_AUTH_TOKEN} ``` --- ## Common Operations ### Creating a Complete Task ```javascript // 1. Get workspace slug const workspacesRes = await fetch('/v1/workspaces', { credentials: 'include' }); const workspaces = await workspacesRes.json(); const workspaceSlug = workspaces.data[0].slug; // 2. Get or create board group const groupsRes = await fetch(`/v1/board-groups?workspaceSlug=${workspaceSlug}`, { credentials: 'include' }); const groups = await groupsRes.json(); const groupId = groups.data[0].id; // 3. Create task const taskRes = await fetch('/v1/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ workspaceSlug, title: 'New Task', boardGroupId: groupId, status: 'todo' }) }); const task = await taskRes.json(); ``` ### Planning a Day ```javascript // Create calendar event linked to a task const eventRes = await fetch('/v1/calendar/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ workspaceSlug, title: 'Deep Work', startsAt: '2024-01-15T09:00:00Z', endsAt: '2024-01-15T11:00:00Z', linkedTaskId: task.data.id }) }); // Start a focus session const sessionRes = await fetch('/v1/focus/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ workspaceSlug, taskId: task.data.id, mode: 'pomodoro', durationSeconds: 1500 }) }); ``` ### Converting Email to Task ```javascript // 1. Get mail messages const messagesRes = await fetch(`/v1/mail/messages?workspaceSlug=${workspaceSlug}&mailboxId=${mailboxId}`, { credentials: 'include' }); const messages = await messagesRes.json(); // 2. Get board groups const groupsRes = await fetch(`/v1/board-groups?workspaceSlug=${workspaceSlug}`, { credentials: 'include' }); const groups = await groupsRes.json(); // 3. Create task from message const taskRes = await fetch(`/v1/mail/messages/${messages.data[0].id}/create-task`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ boardGroupId: groups.data[0].id, title: 'Follow up on email' }) }); ``` --- ## Error Handling Always check for error responses: ```javascript const response = await fetch('/v1/tasks', { ... }); const data = await response.json(); if (data.error) { console.error(`Error [${data.error.code}]: ${data.error.message}`); // Handle error appropriately } ``` **Common Error Codes:** - `UNAUTHORIZED` - Session invalid or expired (401) - `FORBIDDEN` - No access to workspace (403) - `NOT_FOUND` - Resource not found (404) - `VALIDATION_ERROR` - Invalid request data (400) - `CONFLICT` - Business rule conflict (409) - `INTERNAL_ERROR` - Server error (500) --- ## Agent Guidelines 1. **Always verify authentication** before attempting operations - check session status 2. **Ask for workspace context** if not provided (workspace slug) 3. **Handle errors gracefully** and inform the user with clear messages 4. **Respect user authorization** - never attempt operations without proper credentials 5. **Use appropriate HTTP methods**: GET for reading, POST for creating, PATCH for updating, DELETE for removing 6. **Include Content-Type header** for all JSON requests 7. **Include credentials** in fetch requests for cookie-based auth --- ## Development Notes - **Dev Mailbox:** In development, magic links are captured at `/api/dev-mailbox` - **CORS:** Configure `CORS_ALLOW_ORIGINS` for production - **File Uploads:** Max 20MB for attachments - **Session Cookies:** Must be included in requests (`credentials: 'include'` in fetch) --- ## Quick Reference Table | Resource | List | Create | Update | Delete | |----------|------|--------|--------|--------| | Workspaces | `GET /v1/workspaces` | - | - | - | | Tasks | `GET /v1/tasks?workspaceSlug=X` | `POST /v1/tasks` | `PATCH /v1/tasks/{id}` | - | | Board Groups | `GET /v1/board-groups?workspaceSlug=X` | `POST /v1/board-groups` | `PATCH /v1/board-groups/{id}` | - | | Calendar Events | `GET /v1/calendar/events?workspaceSlug=X` | `POST /v1/calendar/events` | `PATCH /v1/calendar/events/{id}` | - | | Notes | `GET /v1/notes?workspaceSlug=X` | `POST /v1/notes` | `PATCH /v1/notes/{id}` | - | | Focus Sessions | `GET /v1/focus/sessions?workspaceSlug=X` | `POST /v1/focus/sessions` | `PATCH /v1/focus/sessions/{id}` | - | | Labels | `GET /v1/labels?workspaceSlug=X` | `POST /v1/labels` | - | - | | Members | `GET /v1/members?workspaceSlug=X` | - | `PATCH /v1/members/{id}` | - | | Invites | `GET /v1/invites?workspaceSlug=X` | `POST /v1/invites` | - | `POST /v1/invites/{token}/revoke` | | Mailboxes | `GET /v1/mailboxes?workspaceSlug=X` | `POST /v1/mailboxes` | - | - | | Mail Messages | `GET /v1/mail/messages?workspaceSlug=X&mailboxId=Y` | - | - | - | | Outgoing Mail | `GET /v1/mail/outgoing?workspaceSlug=X&mailboxId=Y` | `POST /v1/mail/outgoing` | - | - | | Activity | `GET /v1/activity?workspaceSlug=X` | - | - | - | | Health | `GET /v1/health` | - | - | - | | Metrics | `GET /v1/metrics` | - | - | - | --- ## Data Models ### Task ```typescript interface Task { id: string; workspaceSlug: string; boardGroupId: string; title: string; description: string; status: 'todo' | 'in-progress' | 'done'; color: string; dueAt?: string; scheduledStart?: string; scheduledEnd?: string; assigneeId?: string; labelIds: string[]; attachments: Attachment[]; comments: Comment[]; createdAt: string; updatedAt: string; } ``` ### CalendarEvent ```typescript interface CalendarEvent { id: string; workspaceSlug: string; title: string; description: string; startsAt: string; endsAt: string; color: string; linkedTaskId?: string; attachments: Attachment[]; } ``` ### Note ```typescript interface Note { id: string; workspaceSlug: string; title: string; content: string; updatedAt: string; } ``` ### FocusSession ```typescript interface FocusSession { id: string; workspaceSlug: string; taskId?: string; mode: string; startedAt: string; completedAt?: string; pausedAt?: string; pausedTotalSeconds: number; durationSeconds: number; } ``` ### Member ```typescript interface Member { id: string; workspaceSlug: string; name: string; email: string; role: 'owner' | 'admin' | 'member'; status: 'active' | 'inactive'; } ``` ### Attachment ```typescript interface Attachment { id: string; name: string; mimeType: string; size: number; dataURL: string; } ``` ### MailAddress ```typescript interface MailAddress { name: string; email: string; } ```