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

AreaBehavior
CredentialsCLOCKIFY_API_KEY and CLOCKIFY_WORKSPACE_ID in server env (e.g. .env.local). Single workspace for the whole app.
CalendarPer-user: Google OAuth via Supabase provider_token (or env fallback).
Clockify API routesapps/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

AreaBehavior
CredentialsOptional 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.
CalendarUnchanged: per-user Google OAuth.
Clockify API routesFor 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.
UIUser 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_credentials or user_integration_credentials with 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.mdc and playbook.

4.3 Fallback

  • If the authenticated user has no stored credentials, use CLOCKIFY_API_KEY and CLOCKIFY_WORKSPACE_ID from 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

FieldValue
NamePer-User Clockify — Platform Calendar Sync
DescriptionAllow 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 referenceThis 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 titleDescriptionSize / notes
1Add Supabase table for user Clockify credentialsCreate 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
2Add API route: GET/PUT/DELETE user Clockify credentialsAuthenticated 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
3Add resolver: get Clockify config for current requestHelper (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 restIf 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 titleDescriptionSize / notes
5Use resolver in all Clockify API routesReplace 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
6Add request-level tests for per-user vs fallbackTests (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 titleDescriptionSize / notes
7Add “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
8Show 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
9Handle loading and error states for credentials on calendar-clockifyWhile 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 titleDescriptionSize / notes
10Update playbook: per-user vs shared ClockifyIn 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
11Document rollout and optional feature flagShort 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

  1. Create the Linear project (e.g. “Per-User Clockify — Platform Calendar Sync”) and link this plan in the project description.
  2. 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.
  3. Keep tickets small — Prefer 1–2 pt per ticket; avoid a single “Implement per-user Clockify” ticket that spans the whole feature.
  4. 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).
  5. Dependencies — Ticket 2 depends on 1; 3 on 2; 5 on 3; 7–9 on 5. Link blockedBy in 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