Per-User Clockify — Platform Calendar Clockify Sync
Date: 2026-03-03
Status: Draft / Planning
Scope: Platform (The Forge) — /ai-tools/calendar-clockify and Clockify API routes.
1. Purpose
Today, the Platform’s Calendar Clockify Sync uses a single shared Clockify API key and workspace ID from environment variables. All users see the same Clockify user, projects, and time entries when using the tool.
Goal: Support per-user Clockify credentials so each signed-in user can connect their own Clockify API key (and workspace). When they use Calendar Clockify Sync, time entries and project lists come from their Clockify account. Work can be assigned incrementally via small Linear tickets; no single ticket should be “signed out” to a cloud agent as one large deliverable.
2. Current State
| Area | Behavior |
|---|---|
| Credentials | CLOCKIFY_API_KEY and CLOCKIFY_WORKSPACE_ID in server env (e.g. .env.local). Single workspace for the whole app. |
| Calendar | Per-user: Google OAuth via Supabase provider_token (or env fallback). |
| Clockify API routes | apps/platform/src/app/api/brainforge/clockify/* use getClockifyConfig() from _shared.ts, which reads only env. No user context. |
| UI | /ai-tools/calendar-clockify — Calendar Clockify Sync; no UI to set or change Clockify credentials. |
References: apps/platform/src/app/api/brainforge/clockify/_shared.ts, standards/03-knowledge/engineering/setup/calendar-clockify-sync-setup.md.
3. Target State
| Area | Behavior |
|---|---|
| Credentials | Optional per-user Clockify API key (and optionally workspace ID) stored in the platform DB, scoped to the signed-in user. Env vars remain as fallback when user has not connected their own. |
| Calendar | Unchanged: per-user Google OAuth. |
| Clockify API routes | For each request: resolve credentials by (1) authenticated user’s stored credentials, (2) if none, fall back to env. Use resolved credentials for that request only. |
| UI | User can connect (enter API key + optional workspace ID) and disconnect their Clockify account on a settings/connect screen (e.g. on the calendar-clockify page or a dedicated settings section). |
Out of scope (this plan): Clockify OAuth (if Clockify supports it later); MCP server per-user behavior; changes to Snowflake/ETL or Rill.
4. Data Model & Security
4.1 Storage
- Where: Supabase (platform DB). One row per user for Clockify credentials.
- Table (conceptual): e.g.
user_clockify_credentialsoruser_integration_credentialswith columns such as:user_id(UUID, FK to auth.users; primary key or unique)clockify_api_key(text; encrypted at rest if desired — see 4.2)clockify_workspace_id(text, nullable; user can leave blank and use default from Clockify API)updated_at(timestamptz)
- Access: Only the owning user can read/update/delete their row. Enforce via RLS and API checks (session user id === row user_id).
4.2 API Key Handling
- At rest: Prefer encrypting
clockify_api_key(e.g. with a server-side key in env). If encryption is deferred, still restrict access with RLS and never expose the key to the client except in a “masked” or “connected” state. - In use: Server-side only. Never send the raw API key to the browser; only use it in API routes when calling Clockify.
- Secrets rule: No secrets in repo; use env for encryption keys and fallback Clockify env vars. See
.cursor/rules/no-secrets-in-repo.mdcand playbook.
4.3 Fallback
- If the authenticated user has no stored credentials, use
CLOCKIFY_API_KEYandCLOCKIFY_WORKSPACE_IDfrom env (current behavior). This keeps existing deployments working and allows optional rollout of per-user connect.
5. Implementation Phases (High Level)
- Phase 1 — Storage and resolve logic: Schema + migration, API to save/read/delete per-user credentials (with RLS), and a resolve helper used by Clockify routes: “for this request, return either user’s credentials or env fallback.”
- Phase 2 — Wire routes: Change all Clockify API routes to use the resolver (user-scoped when authenticated, else fallback). No UI yet; can be tested with direct API calls or by temporarily setting credentials for a test user.
- Phase 3 — UI: Connect/disconnect UI on the platform (e.g. on calendar-clockify page or settings). User enters API key (and optional workspace ID); stored via the new API. Show “connected” / “not connected” and optionally which workspace.
- Phase 4 — Docs and rollout: Playbook update (calendar-clockify-sync-setup, per-user vs shared), any runbooks, and rollout steps (feature flag optional).
6. Linear Project and Ticket Breakdown
Purpose: Create a single Linear project for this feature and break work into small tickets (typically 0.5–2 days each) so that:
- Work can be assigned and completed incrementally.
- No single ticket is “signed out” to a cloud agent as a full phase; each ticket has clear acceptance criteria and is reviewable.
Team: Platform (or as assigned).
6.1 Linear Project
| Field | Value |
|---|---|
| Name | Per-User Clockify — Platform Calendar Sync |
| Description | Allow each platform user to connect their own Clockify API key and workspace for the Calendar Clockify Sync tool; keep env fallback for shared workspace. |
| Plan reference | This doc: knowledge/plans/per-user-clockify-platform-2026.md |
Create this project in Linear (or add to an existing “Platform — Integrations” style project if you prefer). Below, tickets are written so they can be created as needed (e.g. next 1–3 tickets when starting, then more as the phase progresses).
6.2 Phase 1 — Storage and resolve logic
| # | Ticket title | Description | Size / notes |
|---|---|---|---|
| 1 | Add Supabase table for user Clockify credentials | Create table (e.g. user_clockify_credentials) with user_id, clockify_api_key, clockify_workspace_id, updated_at. Add RLS so only the owning user can SELECT/INSERT/UPDATE/DELETE. Migration in platform repo. | 1 pt |
| 2 | Add API route: GET/PUT/DELETE user Clockify credentials | Authenticated API under e.g. /api/brainforge/clockify/credentials. GET returns masked/“connected” state only (no raw key). PUT accepts apiKey + optional workspaceId; validate with Clockify (e.g. GET /user) before saving. DELETE clears row. Enforce user_id from session. | 2 pt |
| 3 | Add resolver: get Clockify config for current request | Helper (used by Clockify routes): given request/session, return { apiKey, workspaceId } from user credentials if present, else from env. Use in one route first (e.g. GET /api/brainforge/clockify/user) and verify behavior. | 1 pt |
| 4 | (Optional) Encrypt Clockify API key at rest | If not in initial schema: add encryption for clockify_api_key using a server-side key (env); encrypt on write, decrypt in resolver. Document key rotation in playbook. | 1–2 pt |
6.3 Phase 2 — Wire routes
| # | Ticket title | Description | Size / notes |
|---|---|---|---|
| 5 | Use resolver in all Clockify API routes | Replace getClockifyConfig() with “resolve config for this request” in /user, /projects, /time-entries (GET/POST), /calendar-events (if any Clockify calls). Preserve fallback when user has no credentials. | 1 pt |
| 6 | Add request-level tests for per-user vs fallback | Tests (or manual QA checklist): with user A credentials set, request returns A’s data; with no user credentials, request uses env and returns shared workspace data; unauthenticated requests behave as today (e.g. 401 or env fallback per current design). | 1 pt |
6.4 Phase 3 — UI
| # | Ticket title | Description | Size / notes |
|---|---|---|---|
| 7 | Add “Connect Clockify” form on calendar-clockify page (or settings) | Form: API key (password input), optional workspace ID. Submit calls PUT credentials API. Show validation errors (e.g. invalid key from Clockify). Do not expose raw key after save. | 1–2 pt |
| 8 | Show Clockify connection status and “Disconnect” | Display “Connected” (and optionally workspace name from Clockify) when user has credentials; “Not connected” otherwise. Disconnect button calls DELETE credentials API and refreshes state. | 1 pt |
| 9 | Handle loading and error states for credentials on calendar-clockify | While credentials are loading, show neutral state; on error (e.g. 401, 500), show message and retry or link to connect. Ensure sync and project list still work with fallback when not connected. | 1 pt |
6.5 Phase 4 — Docs and rollout
| # | Ticket title | Description | Size / notes |
|---|---|---|---|
| 10 | Update playbook: per-user vs shared Clockify | In standards/03-knowledge/engineering/setup/calendar-clockify-sync-setup.md (and related): document per-user connect flow, that env vars are fallback, where credentials are stored, and that API key is never logged or sent to client. | 1 pt |
| 11 | Document rollout and optional feature flag | Short runbook or README section: how to enable/roll out (e.g. feature flag for “show connect UI” if used), how to verify per-user vs fallback, and how to revoke/rotate. | 1 pt |
6.6 How to use this breakdown
- Create the Linear project (e.g. “Per-User Clockify — Platform Calendar Sync”) and link this plan in the project description.
- Create tickets as needed — When starting, create the first 2–3 tickets (e.g. table + API route + resolver). Add more when moving to Phase 2–4.
- Keep tickets small — Prefer 1–2 pt per ticket; avoid a single “Implement per-user Clockify” ticket that spans the whole feature.
- Agent-friendly — Each ticket can be implemented (or assisted) by an agent with clear acceptance criteria; require human review before closing (e.g. security review for credentials API, UX review for connect UI).
- Dependencies — Ticket 2 depends on 1; 3 on 2; 5 on 3; 7–9 on 5. Link
blockedByin Linear where applicable.
7. Success Criteria
- Each signed-in user can optionally connect their own Clockify API key and workspace.
- Calendar Clockify Sync uses that user’s credentials when set; otherwise falls back to env (shared workspace).
- No raw API key is sent to the client or logged.
- Existing deployments without per-user credentials continue to work unchanged.
- Work is trackable in Linear via small, reviewable tickets.
8. References
- Calendar Clockify Sync Setup
- Platform Clockify API:
apps/platform/src/app/api/brainforge/clockify/ .cursor/rules/no-secrets-in-repo.mdc— no secrets in repo; use env and 1Password