Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with Time Doctor

Connect Bolt.new to Time Doctor using the Time Doctor API v2 with OAuth 2.0 authentication. Fetch worklogs with activity levels, screenshots metadata, and project time breakdowns to build team productivity dashboards. OAuth 2.0 callback requires deployment to a public URL — the Bolt WebContainer preview cannot receive OAuth redirects. For personal access, use API token auth if available on your plan. Time Doctor is designed for employee time monitoring including keyboard, mouse, and app activity data.

What you'll learn

  • How Time Doctor's OAuth 2.0 flow works and why it requires a deployed URL
  • How to fetch worklogs and activity level data from the Time Doctor API
  • How to build a team productivity dashboard showing active vs idle time
  • How to visualize time distribution across projects and categories
  • How to handle Time Doctor's activity percentage data (keyboard/mouse usage)
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate20 min read30 minutesProductivityApril 2026RapidDev Engineering Team
TL;DR

Connect Bolt.new to Time Doctor using the Time Doctor API v2 with OAuth 2.0 authentication. Fetch worklogs with activity levels, screenshots metadata, and project time breakdowns to build team productivity dashboards. OAuth 2.0 callback requires deployment to a public URL — the Bolt WebContainer preview cannot receive OAuth redirects. For personal access, use API token auth if available on your plan. Time Doctor is designed for employee time monitoring including keyboard, mouse, and app activity data.

Build Employee Productivity Dashboards from Time Doctor Data with Bolt.new

Time Doctor occupies a specific category of employee monitoring tools that goes beyond simple time tracking. Where tools like Clockify and Toggl rely on users manually starting and stopping timers, Time Doctor actively monitors keyboard activity, mouse movement, and application usage to calculate an 'activity percentage' for each work interval. Screenshots at configurable intervals provide additional verification for distributed teams. This monitoring-oriented data model makes Time Doctor the choice for organizations managing remote teams that need accountability and productivity metrics alongside time records.

The Time Doctor API v2 exposes this monitoring data through a REST interface. Worklogs (the core time tracking records) include the standard fields you would expect — user, task, project, date, and duration — plus Time Doctor-specific fields: keyboard_and_mouse (activity percentage, 0-100), mobile (whether tracked from mobile), and an associated screenshots object for intervals where screenshots were captured. For managers building oversight dashboards, these additional fields enable visualizations that go well beyond simple hour totals.

The primary integration challenge for Bolt-built applications is authentication. Time Doctor's API v2 requires OAuth 2.0 for multi-user access. The OAuth flow redirects users to Time Doctor's authorization page and then back to a callback URL you specify — this callback URL must be publicly accessible. Bolt's WebContainer preview runs inside a browser tab and cannot receive incoming HTTP redirects from Time Doctor's authorization servers. You must deploy to Netlify or Bolt Cloud first, then register the deployed URL as the OAuth redirect URI in your Time Doctor API credentials. For single-account analytics tools where you control the Time Doctor workspace, some plans provide personal access tokens that bypass the OAuth requirement.

Integration method

Bolt Chat + API Route

Bolt generates Next.js API routes that call Time Doctor's REST API v2. Authentication uses OAuth 2.0 — the callback flow requires deployment to a public URL since the Bolt WebContainer cannot receive OAuth redirects from Time Doctor's authorization server. Once authenticated, API routes fetch worklogs, activity data, and user information as JSON. Personal API token access (available on some Time Doctor plans) bypasses the OAuth requirement for single-account integrations.

Prerequisites

  • A Bolt.new account with a Next.js project
  • A Time Doctor account with API access (Business or Enterprise plan, or a trial)
  • Time Doctor API credentials from app.timedoctor.com/app-integrations
  • Your Time Doctor Company ID (found in account settings or the URL when logged in)
  • A deployed URL on Netlify or Bolt Cloud for the OAuth 2.0 callback (for multi-user apps)

Step-by-step guide

1

Set Up Time Doctor OAuth 2.0 Authentication

Time Doctor's API v2 uses OAuth 2.0 for authentication. The authorization flow involves redirecting users to Time Doctor's authorization page, receiving a callback with an authorization code, and exchanging that code for access and refresh tokens. This multi-step flow requires a publicly accessible callback URL — the Bolt WebContainer cannot receive the OAuth callback from Time Doctor's servers. For multi-user apps: create API credentials in Time Doctor's developer settings at app.timedoctor.com/app-integrations. Click 'Add API application,' provide your app name, and set the redirect URI to your deployed URL + '/api/time-doctor/callback' (e.g., https://yourapp.netlify.app/api/time-doctor/callback). Note your client ID and client secret — these are separate from any personal access token. The OAuth 2.0 authorization URL is https://accounts.timedoctor.com/authorize with parameters: response_type=code, client_id, redirect_uri, scope (use 'openid' plus the scopes you need — 'time:read' for worklogs, 'project:read' for projects), and state (a random value for CSRF protection). After user authorization, Time Doctor redirects to your callback URL with the code parameter. Exchange this code for tokens via POST to https://accounts.timedoctor.com/oauth2/token. For single-account analytics tools where you control the Time Doctor workspace, check if your plan provides a personal API token or if you can generate a long-lived access token for automated access. Some enterprise Time Doctor plans offer an API token that does not require the OAuth flow — store this as TIMEDOCTOR_ACCESS_TOKEN in .env. For development purposes while waiting to deploy, you can use the Time Doctor API v1 (older version) with basic email/password authentication via POST to https://webapi.timedoctor.com/v1.1/login — this is easier to set up initially but the v1 API has fewer features than v2.

Bolt.new Prompt

Create placeholder OAuth endpoints for Time Doctor. Create /api/time-doctor/auth/start/route.ts that builds the Time Doctor OAuth authorization URL using TIMEDOCTOR_CLIENT_ID and redirects to it. Create /api/time-doctor/auth/callback/route.ts that accepts the code param, exchanges it for tokens via POST to Time Doctor's token endpoint, stores the access_token and refresh_token in httpOnly cookies, and redirects to the dashboard. Add clear comments that this requires deployment to work. Also create lib/time-doctor.ts with a timeDoctorFetch helper using the access token from cookies or TIMEDOCTOR_ACCESS_TOKEN env var.

Paste this in Bolt.new chat

lib/time-doctor.ts
1// lib/time-doctor.ts
2// Time Doctor API v2 authentication
3// For OAuth 2.0 multi-user flow, tokens come from cookies set in /api/time-doctor/auth/callback
4// For single-account use, set TIMEDOCTOR_ACCESS_TOKEN in .env
5const TIMEDOCTOR_BASE_URL = 'https://api2.timedoctor.com/api/1.0';
6
7function getToken(cookieToken?: string): string {
8 const token = cookieToken || process.env.TIMEDOCTOR_ACCESS_TOKEN;
9 if (!token) {
10 throw new Error(
11 'Time Doctor access token not found. ' +
12 'Set TIMEDOCTOR_ACCESS_TOKEN in .env for single-account use, ' +
13 'or complete the OAuth 2.0 flow at /api/time-doctor/auth/start.'
14 );
15 }
16 return token;
17}
18
19export async function timeDoctorFetch<T = unknown>(
20 path: string,
21 params?: Record<string, string | number>,
22 token?: string
23): Promise<T> {
24 const accessToken = getToken(token);
25 const url = new URL(`${TIMEDOCTOR_BASE_URL}${path}`);
26 if (params) {
27 Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, String(v)));
28 }
29
30 const response = await fetch(url.toString(), {
31 headers: {
32 Authorization: `Bearer ${accessToken}`,
33 'Content-Type': 'application/json',
34 },
35 });
36
37 if (response.status === 401) {
38 throw new Error('Time Doctor token expired or invalid. Re-authorize via /api/time-doctor/auth/start.');
39 }
40
41 if (!response.ok) {
42 const err = await response.json().catch(() => ({ message: response.statusText })) as { message?: string; error?: string };
43 throw new Error(err.message || err.error || `Time Doctor API error: ${response.status}`);
44 }
45
46 return response.json() as Promise<T>;
47}

Pro tip: Time Doctor access tokens expire after a set period (typically 24 hours for OAuth tokens). Implement token refresh using the refresh_token stored in cookies. When you get a 401 response, call POST https://accounts.timedoctor.com/oauth2/token with grant_type=refresh_token and the stored refresh token to get a new access token without requiring the user to re-authorize.

Expected result: The Time Doctor auth flow is structured. For development with a personal access token, set TIMEDOCTOR_ACCESS_TOKEN in .env and test calls to the API. For multi-user OAuth, deploy first and then test the auth flow with the deployed callback URL.

2

Fetch Worklogs and Activity Data

Time Doctor's worklog endpoint is the primary source of time and activity data. The endpoint returns intervals of tracked work with associated metadata about what the user was doing and how active they were during the interval. For API v2, the worklogs endpoint is GET /api/1.0/activity/worklog with required parameters: company_id (your Time Doctor company ID), start_date and end_date (YYYY-MM-DD format), and optional user_ids for filtering to specific team members. The response contains an array of worklog objects each with: date, user_id, task_id, project_id, start_time, end_time, length (duration in seconds), keyboard (keyboard activity percentage, 0-100), mouse (mouse activity percentage), and mobile (boolean). The activity percentages are measured over the worklog interval. A value of 85 for keyboard means the user's keyboard was in use for 85% of that interval. Time Doctor calculates this from keystroke timestamps rather than continuous monitoring — any keystroke counts as activity for that minute. For dashboard visualization, aggregate activity percentages by user and by day — a weighted average based on interval length gives a more accurate overall activity score than a simple mean. Screenshot data (if your plan includes it) is referenced in worklogs via a screenshots field containing screenshot IDs. Actual screenshot images require separate authenticated API calls and raise significant privacy considerations — ensure your use of screenshot data complies with your organization's privacy policies and any applicable employment laws before building screenshot display features into your Bolt app. For the company ID parameter, find it in your Time Doctor account URL when logged in (https://app.timedoctor.com/{companyId}/dashboard) or in account settings. Store it as TIMEDOCTOR_COMPANY_ID in .env.

Bolt.new Prompt

Create a Next.js API route at app/api/time-doctor/worklogs/route.ts. Accept from, to (YYYY-MM-DD), userId (optional), projectId (optional) query params. Fetch from /activity/worklog with companyId from TIMEDOCTOR_COMPANY_ID env var. Aggregate the response: group by user_id with totalSeconds, avgKeyboardActivity (weighted by interval length), totalIntervals count. Also group by project_id. Return { byUser: [...], byProject: [...], totalCompanyHours }. Cache 60 seconds.

Paste this in Bolt.new chat

app/api/time-doctor/worklogs/route.ts
1// app/api/time-doctor/worklogs/route.ts
2import { NextResponse } from 'next/server';
3import { timeDoctorFetch } from '@/lib/time-doctor';
4
5interface WorklogEntry {
6 userId: string;
7 userName?: string;
8 taskId?: string;
9 projectId?: string;
10 projectName?: string;
11 startTime: string;
12 endTime: string;
13 length: number; // seconds
14 keyboard: number; // 0-100 activity %
15 mouse: number;
16 mobile: boolean;
17}
18
19interface WorklogsResponse {
20 data?: { worklogs?: { items?: WorklogEntry[] } };
21}
22
23const cache = new Map<string, { data: unknown; expiresAt: number }>();
24
25export async function GET(request: Request) {
26 const { searchParams } = new URL(request.url);
27 const from = searchParams.get('from');
28 const to = searchParams.get('to');
29 const userId = searchParams.get('userId');
30 const companyId = process.env.TIMEDOCTOR_COMPANY_ID;
31
32 if (!from || !to) {
33 return NextResponse.json({ error: 'from and to are required' }, { status: 400 });
34 }
35 if (!companyId) {
36 return NextResponse.json({ error: 'TIMEDOCTOR_COMPANY_ID is not configured' }, { status: 500 });
37 }
38
39 const cacheKey = `${from}-${to}-${userId}`;
40 const cached = cache.get(cacheKey);
41 if (cached && Date.now() < cached.expiresAt) {
42 return NextResponse.json(cached.data);
43 }
44
45 try {
46 const params: Record<string, string | number> = {
47 company_id: companyId,
48 start_date: from,
49 end_date: to,
50 'show-tasks': 1,
51 'show-project-ids': 1,
52 };
53 if (userId) params.user_ids = userId;
54
55 const data = await timeDoctorFetch<WorklogsResponse>('/activity/worklog', params);
56 const worklogs = data?.data?.worklogs?.items || [];
57
58 // Aggregate by user with weighted activity average
59 const byUser: Record<string, { name: string; totalSeconds: number; weightedKeyboard: number; totalIntervals: number }> = {};
60 const byProject: Record<string, { name: string; totalSeconds: number }> = {};
61 let totalCompanySeconds = 0;
62
63 for (const log of worklogs) {
64 // By user
65 if (!byUser[log.userId]) {
66 byUser[log.userId] = { name: log.userName || log.userId, totalSeconds: 0, weightedKeyboard: 0, totalIntervals: 0 };
67 }
68 byUser[log.userId].totalSeconds += log.length;
69 byUser[log.userId].weightedKeyboard += log.keyboard * log.length;
70 byUser[log.userId].totalIntervals += 1;
71
72 // By project
73 if (log.projectId) {
74 if (!byProject[log.projectId]) byProject[log.projectId] = { name: log.projectName || log.projectId, totalSeconds: 0 };
75 byProject[log.projectId].totalSeconds += log.length;
76 }
77
78 totalCompanySeconds += log.length;
79 }
80
81 const result = {
82 byUser: Object.entries(byUser).map(([id, v]) => ({
83 id,
84 name: v.name,
85 totalHours: Math.round((v.totalSeconds / 3600) * 100) / 100,
86 avgActivityPercent: v.totalSeconds > 0 ? Math.round(v.weightedKeyboard / v.totalSeconds) : 0,
87 totalIntervals: v.totalIntervals,
88 })).sort((a, b) => b.totalHours - a.totalHours),
89 byProject: Object.entries(byProject).map(([id, v]) => ({
90 id,
91 name: v.name,
92 totalHours: Math.round((v.totalSeconds / 3600) * 100) / 100,
93 })).sort((a, b) => b.totalHours - a.totalHours),
94 totalCompanyHours: Math.round((totalCompanySeconds / 3600) * 100) / 100,
95 };
96
97 cache.set(cacheKey, { data: result, expiresAt: Date.now() + 60_000 });
98 return NextResponse.json(result);
99 } catch (err) {
100 const message = err instanceof Error ? err.message : 'Failed to fetch worklogs';
101 return NextResponse.json({ error: message }, { status: 500 });
102 }
103}

Pro tip: Activity percentages in Time Doctor measure keyboard and mouse input, not whether the user was focused on work-related tasks. A user watching a training video records 0% keyboard activity during the video but is actively working. Educate team members about what activity levels measure before using them as performance metrics to avoid misinterpretation.

Expected result: The worklogs API route fetches Time Doctor data and returns aggregated productivity metrics per user and per project. The weighted activity average more accurately reflects overall engagement than a simple mean of interval percentages.

3

Build a Productivity Dashboard React Component

With the worklogs API route providing aggregated data, build the React dashboard that presents team productivity metrics in a clear, actionable visual format. Productivity dashboards built on monitoring data require careful UX decisions — the goal is to surface insights for improvement, not to create anxiety or punitive metrics. For the team overview, display each team member as a card with their name, total tracked hours, and activity percentage. Use a circular progress indicator or a gauge visualization for the activity percentage — color it contextually but avoid red/green framing that implies good/bad binary judgments. A percentage range of 50-80% is typical for knowledge workers; very high percentages (above 90%) can actually indicate manual data entry work rather than necessarily higher productivity. For the time distribution chart, a stacked horizontal bar chart works well for showing each team member's hours split across projects. Recharts' BarChart with layout='vertical' and stacked bars by project creates an intuitive visualization. Color each project consistently across all bars so the same project is always the same color regardless of which user's bar it appears in. For the daily trend, a line chart showing total team hours per day over the selected period helps identify patterns — lower hours on Mondays and Fridays, dips around holidays, and changes since onboarding new policies. This temporal view often yields more actionable insights than point-in-time snapshots. Add a date range selector with the same quick-pick options as other time tracking dashboards: This Week, Last Week, This Month, Last Month. For manager dashboards, add a team member filter that lets you drill into one person's data. Calculate these date ranges dynamically to keep the dashboard perpetually current without hardcoded dates.

Bolt.new Prompt

Build a ProductivityDashboard React component using data from /api/time-doctor/worklogs. Show: a header with total company hours for the period; a team member grid with cards showing name, hours, and activity % (use a circular gauge colored by range: <50% gray, 50-70% blue, >70% green); a project distribution stacked bar chart (Recharts); and a summary metric showing average team activity percentage. Add This Week / Last Week / This Month date range buttons. Show skeleton loaders while fetching. Include a disclaimer text: 'Activity levels measure keyboard and mouse input, not work quality.'

Paste this in Bolt.new chat

Pro tip: For legal and ethical compliance, ensure all team members whose data is displayed in the dashboard have been informed that Time Doctor monitoring is in use, in accordance with your organization's privacy policy and applicable employment laws. In many jurisdictions, employee monitoring requires explicit disclosure and consent.

Expected result: The dashboard renders with real Time Doctor data showing team productivity metrics, project distribution, and activity level indicators. The date range buttons update all charts simultaneously when clicked.

4

Handle the OAuth 2.0 Callback Flow After Deployment

For production apps where multiple team members or clients log in with their own Time Doctor credentials, the full OAuth 2.0 flow must be implemented with a deployed callback URL. This step cannot be completed in the Bolt WebContainer preview — deploy to Netlify or Bolt Cloud first. The OAuth flow has four steps. Step one: redirect the user to Time Doctor's authorization URL (https://accounts.timedoctor.com/authorize) with your client_id, redirect_uri, response_type=code, scope, and a random state parameter stored in a cookie for CSRF protection. Step two: the user sees Time Doctor's authorization screen and grants permission. Step three: Time Doctor redirects to your callback URL with code and state parameters. Step four: your callback handler verifies the state, then POSTs to https://accounts.timedoctor.com/oauth2/token with the code, client_id, client_secret, and redirect_uri to exchange for access and refresh tokens. Store the access token and refresh token in httpOnly cookies so they are not accessible to JavaScript. Set the access token cookie with an expiry matching the token lifetime (check the expires_in field in the token response). For dashboard applications that need persistent access, implement token refresh: when an API call returns 401, use the refresh token to get a new access token without requiring the user to re-authorize. For security, the state parameter is critical — verify it matches what you set in the authorization redirect before processing the callback. Without state verification, your app is vulnerable to CSRF attacks where an attacker tricks a user's browser into completing an OAuth flow for an attacker-controlled account. Register your redirect URI in Time Doctor's developer console before beginning. The URI must exactly match what you pass in the authorization request — including trailing slashes and path capitalization.

Bolt.new Prompt

Create the OAuth 2.0 callback handler at app/api/time-doctor/auth/callback/route.ts. Accept GET request with code and state params. Verify the state matches the value in the oauth_state cookie. Exchange the code for tokens via POST to https://accounts.timedoctor.com/oauth2/token with client_id, client_secret, code, redirect_uri, grant_type=authorization_code. Store access_token in an httpOnly cookie (1 day expiry) and refresh_token (30 days expiry). Redirect to /dashboard. Add a comment that this only works on deployed apps, not in the Bolt WebContainer preview.

Paste this in Bolt.new chat

app/api/time-doctor/auth/callback/route.ts
1// app/api/time-doctor/auth/callback/route.ts
2// IMPORTANT: This OAuth callback requires deployment to Netlify or Bolt Cloud.
3// Time Doctor cannot redirect to the Bolt WebContainer preview URL.
4// Set TIMEDOCTOR_CLIENT_ID, TIMEDOCTOR_CLIENT_SECRET, and TIMEDOCTOR_REDIRECT_URI in
5// your hosting platform's environment variables after deploying.
6import { NextResponse } from 'next/server';
7import { cookies } from 'next/headers';
8
9interface TokenResponse {
10 access_token: string;
11 refresh_token: string;
12 expires_in: number;
13 token_type: string;
14}
15
16export async function GET(request: Request) {
17 const { searchParams } = new URL(request.url);
18 const code = searchParams.get('code');
19 const state = searchParams.get('state');
20
21 const cookieStore = cookies();
22 const storedState = cookieStore.get('timedoctor_oauth_state')?.value;
23
24 // CSRF protection: verify state matches
25 if (!state || state !== storedState) {
26 return NextResponse.redirect(new URL('/auth-error?reason=state_mismatch', request.url));
27 }
28
29 if (!code) {
30 return NextResponse.redirect(new URL('/auth-error?reason=no_code', request.url));
31 }
32
33 try {
34 const tokenResponse = await fetch('https://accounts.timedoctor.com/oauth2/token', {
35 method: 'POST',
36 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
37 body: new URLSearchParams({
38 grant_type: 'authorization_code',
39 code,
40 client_id: process.env.TIMEDOCTOR_CLIENT_ID || '',
41 client_secret: process.env.TIMEDOCTOR_CLIENT_SECRET || '',
42 redirect_uri: process.env.TIMEDOCTOR_REDIRECT_URI || '',
43 }).toString(),
44 });
45
46 if (!tokenResponse.ok) {
47 throw new Error(`Token exchange failed: ${tokenResponse.status}`);
48 }
49
50 const tokens = await tokenResponse.json() as TokenResponse;
51
52 const response = NextResponse.redirect(new URL('/dashboard', request.url));
53
54 response.cookies.set('td_access_token', tokens.access_token, {
55 httpOnly: true,
56 secure: true,
57 sameSite: 'lax',
58 maxAge: tokens.expires_in,
59 });
60 response.cookies.set('td_refresh_token', tokens.refresh_token, {
61 httpOnly: true,
62 secure: true,
63 sameSite: 'lax',
64 maxAge: 30 * 24 * 60 * 60, // 30 days
65 });
66
67 return response;
68 } catch (err) {
69 console.error('[Time Doctor OAuth] Token exchange error:', err);
70 return NextResponse.redirect(new URL('/auth-error?reason=token_exchange_failed', request.url));
71 }
72}

Pro tip: Set the redirect URI in your Time Doctor developer settings to exactly match the TIMEDOCTOR_REDIRECT_URI environment variable — including the protocol (https://), domain, and path. A mismatch between the registered URI and the one in the token exchange request causes a 400 error with 'redirect_uri_mismatch'. Register both your deployed domain and a localhost URL during development to avoid needing to deploy for every test.

Expected result: The OAuth callback handler processes the authorization code and sets secure httpOnly cookies with the access and refresh tokens. The user is redirected to the dashboard after successful authentication. The tokens are available for subsequent API calls via the timeDoctorFetch helper.

Common use cases

Team Productivity Overview Dashboard

Build a team productivity dashboard that shows each team member's total tracked hours alongside their activity level percentage for a given period. Display a color-coded grid: green for high-activity members (over 70%), yellow for moderate activity (40-70%), and red for low activity (under 40%). Give managers a quick overview of which team members are most engaged during tracked work time.

Bolt.new Prompt

Build a Time Doctor productivity dashboard. Create /api/time-doctor/worklogs that accepts from, to (YYYY-MM-DD), and companyId query params. Call GET https://api2.timedoctor.com/api/1.0/activity/worklog with date range and bearer token auth. Return data grouped by user showing: userName, totalSeconds, totalHours, avgActivityPercent, and a daily breakdown. Build a React team dashboard with user cards showing name, hours tracked, activity percentage as a circular progress indicator (green/yellow/red), and a small sparkline for daily hours trend. Store TIMEDOCTOR_ACCESS_TOKEN and TIMEDOCTOR_COMPANY_ID in process.env.

Copy this prompt to try it in Bolt.new

Project Time Distribution Report

Create a project time allocation report showing how many hours each team member spent on different projects during a billing period. Break down tracked time by project, show each project's percentage of total time, and highlight projects that exceeded their allocated hours budget. Useful for project managers reviewing resource allocation and planning upcoming work.

Bolt.new Prompt

Build a project time distribution report using Time Doctor. Create /api/time-doctor/projects that fetches project time data for a date range. Call GET /api/1.0/projects/{companyId} to get project list, then GET /api/1.0/activity/worklog filtered by project. Group by project name, calculate totalHours and percentOfTotal. Build a React report with a pie chart (Recharts PieChart) showing time distribution by project and a detail table below showing project name, hours, percentage, and team member count. Add a date range selector.

Copy this prompt to try it in Bolt.new

Individual Employee Time Summary

Build a self-service view where individual employees can see their own tracked time, activity levels, and project distribution for the current week. Display a daily timeline showing work intervals with activity percentages, a project breakdown, and comparison to previous week. This gives employees insight into their own Time Doctor data in a friendlier interface than Time Doctor's native reporting.

Bolt.new Prompt

Create an employee time summary page using Time Doctor API. Build /api/time-doctor/my-summary that fetches worklogs for a specific userId (from TIMEDOCTOR_USER_ID env var) for the current week. Return: totalHours, avgActivityPercent, dailyBreakdown (date, hours, activityPercent per day), projectBreakdown (project name, hours, percent). Build a React summary page with a header showing weekly hours and activity score, a bar chart of daily hours (Recharts), a horizontal bar chart of project distribution, and a comparison badge showing +/-% vs last week.

Copy this prompt to try it in Bolt.new

Troubleshooting

OAuth redirect returns to Bolt WebContainer URL instead of the deployed callback

Cause: The Time Doctor OAuth flow redirects to the callback URL registered in the developer console. If the registered URL points to the Bolt preview URL or is not yet configured, the redirect fails or goes to the wrong place.

Solution: Deploy to Netlify or Bolt Cloud first. Update the redirect URI in Time Doctor's developer console to your deployed URL (e.g., https://yourapp.netlify.app/api/time-doctor/auth/callback). Set TIMEDOCTOR_REDIRECT_URI in your hosting environment variables to match. The WebContainer preview URL cannot be used as an OAuth callback URI.

401 Unauthorized after a successful OAuth flow — access token not found in API requests

Cause: The access token stored in httpOnly cookies is not being read correctly in the API route context, or the cookie was not set with the correct sameSite and secure flags causing it not to be sent with requests.

Solution: Read the cookie in the API route using Next.js cookies() from 'next/headers'. Verify the cookie is being set with the correct flags during the OAuth callback. For single-account use during development, set TIMEDOCTOR_ACCESS_TOKEN directly in .env as a fallback to cookies.

typescript
1// Read Time Doctor token from cookies in API route:
2import { cookies } from 'next/headers';
3
4export async function GET() {
5 const cookieStore = cookies();
6 const token = cookieStore.get('td_access_token')?.value
7 || process.env.TIMEDOCTOR_ACCESS_TOKEN;
8
9 if (!token) {
10 return NextResponse.json({ error: 'Not authenticated', redirect: '/api/time-doctor/auth/start' }, { status: 401 });
11 }
12 // ... proceed with API call
13}

Worklogs API returns empty data or missing users

Cause: The TIMEDOCTOR_COMPANY_ID is incorrect, or the date range spans time before the company account was created. Also, users whose accounts have been deactivated in Time Doctor may not appear in worklog responses.

Solution: Verify TIMEDOCTOR_COMPANY_ID by checking the URL when logged into Time Doctor (it appears in the path). Test with today's date as both from and to first to confirm data exists for the current day. Check that the users you expect to see are active (not archived) in your Time Doctor account settings.

Activity percentage values are consistently 0 for all users

Cause: Activity tracking (keyboard and mouse monitoring) may not be enabled for the Time Doctor account, or users are tracking time via manual timer rather than the automatic tracker. Some Time Doctor plans or configurations disable keyboard and mouse monitoring.

Solution: Check the Time Doctor account settings to confirm activity monitoring is enabled. Verify team members are using the Time Doctor desktop or mobile app (not just the web timer) since activity monitoring requires the native app. If activity monitoring is intentionally disabled for privacy reasons, remove activity percentage fields from your dashboard to avoid showing misleading zeros.

Best practices

  • Inform all employees whose data will be displayed in the dashboard that Time Doctor monitoring is active and explain what metrics are collected — this is a legal requirement in many jurisdictions for employee monitoring tools.
  • Never expose TIMEDOCTOR_ACCESS_TOKEN, TIMEDOCTOR_CLIENT_SECRET, or OAuth tokens with the NEXT_PUBLIC_ prefix — these credentials grant access to sensitive employee monitoring data.
  • Interpret activity percentage contextually — knowledge workers, video meetings, and deep work tasks naturally produce lower activity percentages than data entry roles, and 100% activity does not equal high performance.
  • Implement token refresh logic for OAuth flows — Time Doctor access tokens expire, and requiring users to re-authorize daily creates a poor user experience for persistent dashboard tools.
  • Cache Time Doctor API responses for 60+ seconds — productivity data does not require real-time updates and caching significantly reduces API load for dashboards with multiple concurrent viewers.
  • Build the OAuth callback flow only after deploying — testing OAuth redirects in the Bolt WebContainer is impossible and wastes development time. Deploy to Netlify or Bolt Cloud, then test the full auth flow.
  • Aggregate activity data with weighted averages by duration rather than simple means — a 10-minute interval with 90% activity should have less influence than a 2-hour interval with 70% activity in overall calculations.
  • Provide clear documentation in your dashboard UI about what activity percentages measure and do not measure, to prevent management from using these metrics in ways that harm team morale or create surveillance-based performance anxiety.

Alternatives

Frequently asked questions

Why does the Time Doctor OAuth flow require deployment — can I test it in Bolt's preview?

No — the OAuth 2.0 callback flow requires a publicly accessible HTTPS URL that Time Doctor can redirect to after user authorization. Bolt's WebContainer preview runs inside a browser tab at a URL like https://[hash].local.webcontainer-api.io/ which cannot be registered as an OAuth redirect URI and cannot receive incoming HTTP redirects from Time Doctor's servers. Deploy to Netlify or Bolt Cloud first, then test the full OAuth flow with your deployed URL.

Can I use Time Doctor API without the OAuth flow for a single-account dashboard?

It depends on your Time Doctor plan and version. Time Doctor API v1 (older) supported email and password authentication that does not require OAuth. Some enterprise Time Doctor plans provide personal access tokens or long-lived API tokens. Check your Time Doctor developer settings for token options. If available, set the token as TIMEDOCTOR_ACCESS_TOKEN in .env to bypass the OAuth requirement entirely.

What does the activity percentage actually measure in Time Doctor?

Time Doctor's activity percentage measures keyboard and mouse input during tracked work intervals. During a minute of tracked time, if the keyboard or mouse was used for 45 seconds, the activity percentage for that minute is approximately 75%. It does not measure whether the user was doing productive work — watching training videos, reading documentation, or thinking through a problem all register as low activity even though they are legitimate work activities.

Does Bolt.new have a native Time Doctor integration?

No — Bolt.new does not have a built-in Time Doctor connector. The integration uses Time Doctor's REST API directly. Bolt's AI can generate the integration code, but the OAuth 2.0 requirement means you need to deploy before testing the full authentication flow.

Can I display Time Doctor screenshots in my Bolt dashboard?

The Time Doctor API provides screenshot metadata (timestamps and screenshot IDs) in worklog responses when screenshots are enabled on your plan. Displaying the actual screenshot images requires additional API calls to fetch the image data and careful consideration of employee privacy. Before building screenshot display features, confirm that all relevant employees have been informed about screenshot capture and that your use complies with applicable employment laws and your organization's privacy policy.

How do I get my Time Doctor Company ID?

Your Time Doctor Company ID appears in the URL when you log into the Time Doctor web app — it is the numeric ID in the path like https://app.timedoctor.com/1234567/dashboard. You can also find it in Account Settings → Company Info. Store this as TIMEDOCTOR_COMPANY_ID in your .env file since it is required for most API endpoints as a query parameter.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.