To integrate Clockify with Bolt.new, create a Next.js API route that calls the Clockify REST API using your personal API key from Profile Settings. Outbound calls (fetching time entries, projects, creating entries) work fully in the Bolt preview. Build a time tracking dashboard by querying /workspaces/{id}/time-entries, /projects, and /reports/summary endpoints — all plain HTTP requests requiring only an API key header.
Building a Time Tracking Dashboard with Clockify and Bolt.new
Clockify is one of the most popular free time tracking tools, used by over 5 million users to log hours across projects and generate productivity reports. Unlike many SaaS APIs that require OAuth flows and complex authentication setups, Clockify uses a simple API key model — you add one header (X-Api-Key) to every request and you're authenticated. This simplicity makes it an excellent fit for Bolt.new projects.
The Clockify REST API gives you programmatic access to the full platform: fetch all workspaces, list projects and tasks, read time entries for any date range, create new time entries on behalf of users, manage team members, and generate detailed summary or detailed reports. The 'clockify to json' search query reveals what users actually want — they need programmatic access to extract time tracking data into their own applications, dashboards, or reporting tools. This tutorial covers the most practical patterns: pulling time entry data, building a summary dashboard, and creating entries.
Since all Clockify API calls are outbound HTTPS requests from your Next.js API route, they work perfectly in Bolt's WebContainer preview environment. You can build and test the entire integration before deploying. The only Clockify feature that requires deployment is webhooks — if you want Clockify to push events (new time entry created, timer started) to your app in real time. For most dashboards and reporting use cases, polling works well and does not require deployment.
Integration method
Clockify uses simple API key authentication — a single header on every request. All Clockify REST endpoints communicate over HTTPS and are fully accessible from Bolt's WebContainer via Next.js API routes. Outbound calls to fetch time entries, projects, tasks, and generate reports work during development in the Bolt preview. Webhooks from Clockify (time entry created/updated events) require a deployed URL since the WebContainer has no public address.
Prerequisites
- A Clockify account (free tier includes full API access)
- Your Clockify API key from Profile Settings → API section
- Your workspace ID (visible in the Clockify web app URL or from the /workspaces API endpoint)
- A Bolt.new project using Next.js for server-side API routes
- At least one project and some time entries in Clockify to test with
Step-by-step guide
Get Your Clockify API Key and Workspace ID
Get Your Clockify API Key and Workspace ID
Clockify API authentication is straightforward — there is no OAuth dance, no client secret rotation, no token exchange. You get a single API key from your profile and use it as a header on every request. To find your API key: log into app.clockify.me, click your profile avatar in the top-right corner, then click 'Profile Settings'. Scroll to the bottom of the page to the API section. Click 'Generate' if no key exists, or copy your existing key. This key authenticates as you — it has the same permissions as your account. Keep it secret and never expose it in client-side code. Next, you need your workspace ID. Every Clockify account has at least one workspace, and all API calls are scoped to a workspace. The easiest way to get your workspace ID is to look at the URL when you're in the Clockify web app — it appears as a 24-character hex string after /workspaces/. Alternatively, make a GET request to https://api.clockify.me/api/v1/workspaces with your API key header — the response contains your workspace ID and name in JSON. Copy the ID of the workspace you want to use.
1# Test your API key with curl (optional — for verification only)2# GET https://api.clockify.me/api/v1/workspaces3# Header: X-Api-Key: your_api_key45# The response will look like:6# [{"id": "your_workspace_id", "name": "Your Workspace", ...}]Pro tip: If you belong to multiple Clockify workspaces (e.g., your own account plus a client's workspace), make sure you copy the ID of the correct workspace. Each workspace has separate projects and time entries.
Expected result: You have a Clockify API key (32-character string) and a workspace ID (24-character hex string) ready to add to your environment variables.
Configure Environment Variables and Create a Clockify Client Utility
Configure Environment Variables and Create a Clockify Client Utility
With your API key and workspace ID in hand, configure your Bolt project's environment variables and create a reusable Clockify client. In a Next.js project, server-side environment variables (without the NEXT_PUBLIC_ prefix) are only readable in API routes and server components — this is exactly where Clockify credentials should live. Never access your Clockify API key from a client component or add NEXT_PUBLIC_ to it, as this would expose it in your JavaScript bundle. Creating a shared Clockify client utility in lib/clockify.ts avoids repeating headers in every API route. The client wraps the native fetch API, automatically adding the X-Api-Key header and the base URL. It also handles error responses from Clockify's API — the API returns standard HTTP status codes (401 for bad API key, 403 for missing permissions, 404 for invalid IDs, 429 for rate limit exceeded). Centralizing error handling here keeps your API routes clean. Rate limits: Clockify allows 50 requests per second on free plans and 10 requests per second for enterprise — well above what a dashboard app would need.
Create a .env file with CLOCKIFY_API_KEY and CLOCKIFY_WORKSPACE_ID. Create a lib/clockify.ts utility that exports a clockifyFetch function that wraps the native fetch with the X-Api-Key header from CLOCKIFY_API_KEY and the base URL https://api.clockify.me/api/v1. Also export WORKSPACE_ID that reads from CLOCKIFY_WORKSPACE_ID. Include proper TypeScript types for the function signature.
Paste this in Bolt.new chat
1// lib/clockify.ts2const BASE_URL = 'https://api.clockify.me/api/v1';3export const WORKSPACE_ID = process.env.CLOCKIFY_WORKSPACE_ID!;45export async function clockifyFetch<T>(6 path: string,7 options: RequestInit = {}8): Promise<T> {9 const apiKey = process.env.CLOCKIFY_API_KEY;10 if (!apiKey) throw new Error('CLOCKIFY_API_KEY is not set');1112 const response = await fetch(`${BASE_URL}${path}`, {13 ...options,14 headers: {15 'X-Api-Key': apiKey,16 'Content-Type': 'application/json',17 ...options.headers,18 },19 });2021 if (!response.ok) {22 const text = await response.text();23 throw new Error(`Clockify API error ${response.status}: ${text}`);24 }2526 return response.json() as Promise<T>;27}Expected result: The .env file has CLOCKIFY_API_KEY and CLOCKIFY_WORKSPACE_ID set. The lib/clockify.ts utility is ready to import into any API route.
Create API Routes for Projects and Time Entries
Create API Routes for Projects and Time Entries
With the Clockify client ready, build the core API routes your frontend will call. You need at minimum two routes: one to fetch projects (for dropdowns and filtering) and one to fetch time entries for a date range. Clockify's time entries endpoint supports pagination (default 50 entries per page, max 5000) and filtering by date range using ISO 8601 datetime strings with the start and end query parameters. The projects endpoint returns all projects in your workspace including their IDs, names, client associations, color codes, estimated hours, and archived status. Store the project list in your frontend state so you don't need to re-fetch it on every render — projects don't change frequently. For time entries, always filter by date range rather than fetching all entries. A workspace with months of data will return thousands of entries without a date filter, which is slow and wastes API quota. Time entries come back with start/end datetime strings, duration in seconds (as a negative number — this is a Clockify quirk for running timers), project ID, task ID, and description. You'll need to join project IDs with your project list to display project names. The duration field uses ISO 8601 duration format (PT2H30M = 2 hours 30 minutes) for completed entries.
Create two Next.js API routes for Clockify. First, /api/clockify/projects (GET) fetches all projects from /workspaces/{workspaceId}/projects using clockifyFetch, returns them sorted by name. Second, /api/clockify/entries (GET) accepts startDate and endDate query params, fetches time entries from /workspaces/{workspaceId}/user/{userId}/time-entries with date-range filter, and returns entries with projectId, description, timeInterval (start, end, duration). First fetch the current user from /user to get userId. Include proper TypeScript interfaces for ClockifyProject and ClockifyTimeEntry.
Paste this in Bolt.new chat
1// app/api/clockify/entries/route.ts2import { NextResponse } from 'next/server';3import { clockifyFetch, WORKSPACE_ID } from '@/lib/clockify';45interface ClockifyUser { id: string; email: string; name: string; }6interface TimeInterval { start: string; end: string | null; duration: string | null; }7interface ClockifyTimeEntry {8 id: string;9 description: string;10 projectId: string | null;11 timeInterval: TimeInterval;12 billable: boolean;13}1415export async function GET(request: Request) {16 try {17 const { searchParams } = new URL(request.url);18 const start = searchParams.get('startDate') || new Date(Date.now() - 7 * 86400000).toISOString();19 const end = searchParams.get('endDate') || new Date().toISOString();2021 const user = await clockifyFetch<ClockifyUser>('/user');22 const entries = await clockifyFetch<ClockifyTimeEntry[]>(23 `/workspaces/${WORKSPACE_ID}/user/${user.id}/time-entries?start=${start}&end=${end}&page-size=500`24 );2526 return NextResponse.json(entries);27 } catch (error: unknown) {28 const e = error as { message: string };29 return NextResponse.json({ error: e.message }, { status: 500 });30 }31}Pro tip: Clockify returns time entries newest-first by default. If you want chronological order for a timeline view, reverse the array or sort by timeInterval.start.
Expected result: GET /api/clockify/projects returns your workspace's project list as JSON. GET /api/clockify/entries?startDate=...&endDate=... returns time entries for that range. Both work in the Bolt preview.
Build the Time Tracking Summary Dashboard
Build the Time Tracking Summary Dashboard
With the API routes in place, build a dashboard that aggregates time entries and displays them meaningfully. The most useful view groups time entries by project and calculates total hours per project for a selected date range. This gives a quick answer to 'how much time did we spend on each project this week?' — the most common time tracking question. The aggregation logic runs client-side: fetch all time entries for the date range, group them by projectId, sum up the durations, then join with the project list to get project names. Duration parsing is the trickiest part — Clockify returns ISO 8601 duration strings (PT1H30M15S = 1 hour 30 minutes 15 seconds) for completed entries and null for running timers. Parse these into seconds for arithmetic, then format the totals back into human-readable hours and minutes. Adding a week/month date range picker dramatically improves usability. Use a simple select dropdown for preset ranges (This Week, Last Week, This Month, Last Month) rather than a full date picker to keep the UI simple. The Bolt AI can generate the date calculation logic for each preset. Fetching both projects and entries in parallel with Promise.all reduces the dashboard load time.
Build a time tracking summary dashboard page at /dashboard/time. It should: fetch projects from /api/clockify/projects and time entries from /api/clockify/entries with startDate/endDate params. Show a date range selector with options: This Week, Last Week, This Month, Last Month. Display a summary table with columns: Project Name, Total Hours, Number of Entries. Parse the Clockify ISO duration strings (PT1H30M15S format) into hours/minutes. Show a total hours card at the top. Loading skeleton while fetching.
Paste this in Bolt.new chat
1// utils/clockify-duration.ts2export function parseDuration(isoDuration: string | null): number {3 if (!isoDuration) return 0;4 // PT1H30M15S -> seconds5 const match = isoDuration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);6 if (!match) return 0;7 const hours = parseInt(match[1] || '0');8 const minutes = parseInt(match[2] || '0');9 const seconds = parseInt(match[3] || '0');10 return hours * 3600 + minutes * 60 + seconds;11}1213export function formatDuration(seconds: number): string {14 const h = Math.floor(seconds / 3600);15 const m = Math.floor((seconds % 3600) / 60);16 return `${h}h ${m.toString().padStart(2, '0')}m`;17}Pro tip: Running timers (entries without an end time) have a null duration. Exclude them from totals or estimate their duration using Date.now() minus the start time to show a live counter.
Expected result: The dashboard shows a project-grouped summary table with total hours per project for the selected date range. Switching the date range selector re-fetches and updates the table instantly.
Add Time Entry Creation and Deploy for Webhook Support
Add Time Entry Creation and Deploy for Webhook Support
Creating time entries programmatically lets users log time directly from your app. The Clockify API accepts POST requests to /workspaces/{workspaceId}/time-entries with a JSON body containing the start time, end time (or duration), project ID, task ID (optional), description, and billable flag. All datetime values must be in UTC ISO 8601 format. The entry creation API route is straightforward: validate the required fields, call the Clockify API, and return the created entry. One important validation: if both end and duration are provided, Clockify uses end. If only duration is provided without end, Clockify calculates the end time. Always send start and end for completed entries. For creating a running timer (no end time), omit the end field entirely. Webhooks are the only part of this integration that requires deployment. If you want real-time notifications when team members start or stop timers — for example, to update a live dashboard or trigger notifications — Clockify needs to send HTTP POST requests to your app. Since Bolt's WebContainer has no public URL, webhooks cannot reach it during development. Deploy to Netlify or Bolt Cloud, then configure webhooks in Clockify's workspace settings under Integrations → Webhooks, pointing to your deployed /api/clockify/webhook endpoint.
Create an API route at /api/clockify/entries (POST) that creates a new time entry in Clockify. Accept projectId (required), description, start (ISO datetime, required), end (ISO datetime, required), and billable (boolean, defaults to false). Validate required fields. POST to Clockify /workspaces/{workspaceId}/time-entries with the data. Return the created entry. Also add a 'Log Time' button and modal form to the dashboard with project dropdown, description input, date/time pickers for start and end, and billable checkbox.
Paste this in Bolt.new chat
1// app/api/clockify/entries/route.ts — POST handler (add to existing file)2import { clockifyFetch, WORKSPACE_ID } from '@/lib/clockify';3import { NextResponse } from 'next/server';45export async function POST(request: Request) {6 try {7 const body = await request.json();8 const { projectId, description, start, end, billable = false } = body;910 if (!start || !end) {11 return NextResponse.json(12 { error: 'start and end are required' },13 { status: 400 }14 );15 }1617 const entry = await clockifyFetch(18 `/workspaces/${WORKSPACE_ID}/time-entries`,19 {20 method: 'POST',21 body: JSON.stringify({22 start,23 end,24 description: description || '',25 projectId: projectId || null,26 billable,27 }),28 }29 );3031 return NextResponse.json(entry, { status: 201 });32 } catch (error: unknown) {33 const e = error as { message: string };34 return NextResponse.json({ error: e.message }, { status: 500 });35 }36}Pro tip: Clockify requires datetime values in UTC. If your users are entering local times, convert them to UTC before sending: new Date(localDatetimeString).toISOString() handles this correctly in JavaScript.
Expected result: POST to /api/clockify/entries creates a real time entry in Clockify and returns it. The entry appears in the Clockify web app immediately. The Log Time modal in your dashboard works for creating entries during development.
Common use cases
Team Time Tracking Dashboard
Build an internal dashboard that shows hours logged per project across your team for the current week or month. Pull data from Clockify's summary report endpoint grouped by project, display progress bars against estimated hours, and highlight projects that are over budget. Updates on demand by re-querying the API.
Build a time tracking dashboard that shows hours per project for this week. Create an API route at /api/clockify/summary that calls the Clockify summary report endpoint for my workspace with the current week date range, grouped by PROJECT. Display the results as a list of project cards showing project name, total hours this week, and a progress bar against a target of 40 hours. Use CLOCKIFY_API_KEY and CLOCKIFY_WORKSPACE_ID from environment variables.
Copy this prompt to try it in Bolt.new
Automatic Time Entry Logger
Create a form where users can log time entries directly from your app without opening Clockify. They select a project, enter a description and duration, and the entry is created in Clockify via the API. Useful for project management tools that want to add time logging without leaving the platform.
Add a 'Log Time' modal to my project management app. Create an API route at /api/clockify/entries (POST) that accepts projectId, description, start (ISO datetime), and duration (in seconds) then creates a time entry in Clockify using CLOCKIFY_API_KEY and CLOCKIFY_WORKSPACE_ID. Build a form with: project dropdown (fetched from /api/clockify/projects), description text input, date picker, and duration input (hours and minutes). Submit creates the entry and shows a success toast.
Copy this prompt to try it in Bolt.new
Billable Hours Report Export
Generate a weekly or monthly billable hours report grouped by client/project and export it as JSON or CSV. Finance teams use this to generate invoices without manually exporting from Clockify every time. The API route queries the detailed report endpoint with a custom date range and formats the data for export.
Create a billable hours report page. Build an API route at /api/clockify/report that accepts startDate and endDate query params, calls the Clockify detailed report endpoint filtered to billable=true, and returns entries grouped by project with total hours and billable amounts. Add a date range picker UI and a 'Export CSV' button that downloads the report. Use CLOCKIFY_API_KEY and CLOCKIFY_WORKSPACE_ID from environment variables.
Copy this prompt to try it in Bolt.new
Troubleshooting
API returns 401 Unauthorized or 'Full authentication is required'
Cause: The CLOCKIFY_API_KEY environment variable is missing, empty, or the API key was regenerated in Clockify after you copied it.
Solution: Go to Clockify Profile Settings → API and verify your key. Copy it again — any regeneration invalidates the old key. Make sure CLOCKIFY_API_KEY is set in your .env file without quotes. Restart the Bolt dev server after editing .env since Next.js reads environment variables at startup.
1// Add to lib/clockify.ts for better error messages:2if (!process.env.CLOCKIFY_API_KEY) {3 throw new Error('CLOCKIFY_API_KEY environment variable is not set');4}Time entries fetch returns an empty array even though entries exist in Clockify
Cause: The date range filter is using the wrong timezone or format. Clockify requires UTC ISO 8601 datetimes. Passing a local date string without timezone info results in unexpected filtering.
Solution: Always use UTC ISO 8601 format for date range parameters: new Date().toISOString() gives the correct format. For 'this week', calculate Monday 00:00:00 UTC and Sunday 23:59:59 UTC explicitly. Also verify CLOCKIFY_WORKSPACE_ID is correct — entries from a different workspace won't appear.
1// Correct date range for this week in UTC:2const now = new Date();3const dayOfWeek = now.getUTCDay();4const monday = new Date(now);5monday.setUTCDate(now.getUTCDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1));6monday.setUTCHours(0, 0, 0, 0);7const start = monday.toISOString(); // '2025-03-24T00:00:00.000Z'Duration shows as 'PT' or '0h 00m' for time entries
Cause: The time entry is a running timer (no end time set) so duration is null, or the entry was just started and has zero duration. The parseDuration function returns 0 for null inputs.
Solution: Filter out running timers (entries where timeInterval.end is null) before calculating totals. Display running timers separately with a live elapsed time counter calculated from Date.now() minus the start time.
1// Filter completed vs running entries:2const completedEntries = entries.filter(e => e.timeInterval.end !== null);3const runningTimer = entries.find(e => e.timeInterval.end === null);Clockify webhooks not firing during development in Bolt preview
Cause: This is the expected WebContainer limitation. Clockify webhooks make outbound HTTP POST calls to your app when events occur. Bolt's WebContainer runs inside a browser tab with no publicly accessible URL, so Clockify cannot reach it.
Solution: Deploy your app to Netlify or Bolt Cloud first. After deployment, go to your Clockify Workspace Settings → Integrations → Webhooks, add a new webhook pointing to your deployed URL (e.g., https://your-app.netlify.app/api/clockify/webhook), and select the events you want to receive.
Best practices
- Store CLOCKIFY_API_KEY as a server-side environment variable only — never add NEXT_PUBLIC_ prefix since this would expose it in client JavaScript bundles
- Always filter time entries by date range rather than fetching all entries — workspaces with months of data can have thousands of entries, making unfiltered requests slow and expensive
- Cache project lists in your application state since projects change infrequently — re-fetch every 5 minutes or on user action rather than on every page load
- Convert all datetime values to UTC before sending to the Clockify API — use new Date(localString).toISOString() to ensure correct timezone handling regardless of the user's locale
- Handle null duration values gracefully — running timers have no end time and return null for duration, so calculate elapsed time from start using Date.now() for live timer display
- Use the Clockify summary report endpoints (/reports/summary) for aggregated dashboards rather than fetching all entries and summing client-side — summary reports are faster for large workspaces
- Test your integration with a date range that has known data so you can verify the numbers match what Clockify shows in its own interface
Alternatives
Toggl offers a similar time tracking API with strong reporting features and is popular among freelancers who prefer its simpler UI over Clockify's workspace model.
Harvest combines time tracking with invoicing and expense tracking, making it better for agencies that need to bill clients directly from tracked hours.
Everhour integrates natively inside project management tools like Asana and Trello, making it the choice when time tracking must live alongside task management.
Time Doctor adds employee monitoring features like screenshots and website tracking on top of time logging, aimed at distributed teams needing accountability tools.
Frequently asked questions
Does the Clockify API work in Bolt's WebContainer during development?
Yes. Clockify's REST API communicates over HTTPS, which works perfectly in Bolt's WebContainer. All outbound API calls — fetching projects, time entries, creating entries, and pulling reports — work in the Bolt preview. The only thing that requires deployment is receiving Clockify webhook events, since the WebContainer has no public URL for Clockify to call.
How do I find my Clockify workspace ID?
Log into app.clockify.me and look at the URL — your workspace ID appears as a 24-character hex string in the path. Alternatively, call GET https://api.clockify.me/api/v1/workspaces with your X-Api-Key header and the response includes your workspace ID and name as JSON. If you belong to multiple workspaces, the workspaces array contains all of them.
Is the Clockify API free to use?
Yes. Clockify's REST API is available on all plans including the free tier. The free plan supports unlimited users and unlimited API calls. Paid plans (Standard at $5.49/user/month, Pro at $7.99/user/month) add features like required fields, time rounding, and advanced reports, but API access itself is not gated behind a paywall.
Can I read and write time entries for other team members?
Yes, but only if your Clockify account has the Manager or Owner role in the workspace. With manager permissions, you can fetch time entries for any user by using their user ID instead of yours in the /workspaces/{workspaceId}/user/{userId}/time-entries endpoint. Get all workspace users from /workspaces/{workspaceId}/users to build a user selector in your dashboard.
How do I generate a report for a date range in Clockify's API?
Use the reports endpoint: POST https://reports.api.clockify.me/v1/workspaces/{workspaceId}/reports/summary with a JSON body containing dateRangeStart, dateRangeEnd (ISO 8601 UTC strings), and a summaryFilter object specifying groups like PROJECT or USER. The reports API uses the same X-Api-Key header but a different base URL (reports.api.clockify.me) than the main API (api.clockify.me).
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation