Connect FlutterFlow to Time Doctor using the API Calls panel with OAuth 2.0 v2 authentication, but the token exchange and refresh must run in a Firebase Cloud Function — not in the FlutterFlow client. Use the v2 base URL (https://api2.timedoctor.com/api/1.0), pass Bearer tokens and your company ID as required path segment, and paginate worklog responses with date-range query variables to build a remote team time-tracking dashboard.
| Fact | Value |
|---|---|
| Tool | Time Doctor |
| Category | Productivity |
| Method | FlutterFlow API Call |
| Difficulty | Advanced |
| Time required | 90 minutes |
| Last updated | July 2026 |
Build a Remote Team Time Dashboard in FlutterFlow with Time Doctor
Time Doctor is a widely used time tracking tool for remote and distributed teams — it monitors time spent on tasks, tracks app and website usage, and optionally captures screenshots at intervals. Many managers want a mobile companion app to check their team's daily worklogs without sitting at a computer. FlutterFlow is a natural fit for building this kind of internal dashboard that works on both iOS and Android.
The key challenge in this integration is Time Doctor's version landscape. There are two active API versions: v1.1 (https://webapi.timedoctor.com/v1.1 or https://api2.timedoctor.com/v1.1) which uses a simpler API token from account settings, and v2 (https://api2.timedoctor.com/api/1.0) which uses OAuth 2.0 with Bearer tokens. This tutorial uses v2, which is Time Doctor's current recommended API and the one supported for new integrations. Mixing v1.1 URLs with v2 tokens causes confusing 401 errors — commit to one version from the start.
Pricing for Time Doctor starts from approximately $7/user/month for the Basic plan (verify current tiers at timedoctor.com). API access is available on paid plans. The integration architecture for FlutterFlow follows the same OAuth broker pattern as Basecamp: a Firebase Cloud Function handles the client secret, does the token exchange, and stores and refreshes access tokens. FlutterFlow calls the function for a valid token, then uses that token in the API Calls panel to hit Time Doctor endpoints. Most Time Doctor v2 endpoints require a company ID in the URL path — fetching it once and storing it in App State is an important early step.
Integration method
Time Doctor v2 uses OAuth 2.0 with Bearer tokens. Because the token exchange requires a client secret that must never ship in a Flutter app, the OAuth handshake and token refresh live in a Firebase Cloud Function. FlutterFlow's API Calls panel then calls the Time Doctor v2 REST API (https://api2.timedoctor.com/api/1.0) using the token and a mandatory company ID in the URL path. Worklog responses are paginated with date-range query parameters, and the results bind to FlutterFlow Data Types that power a team dashboard with charts and ListViews.
Prerequisites
- A Time Doctor account on a paid plan (Basic ~$7/user/month or higher) with API access enabled
- Time Doctor v2 OAuth credentials — register an app in the Time Doctor integrations portal or contact Time Doctor support for v2 API access
- Your Time Doctor company ID — visible in the dashboard URL after logging in (https://2.timedoctor.com/companyID)
- A Firebase project with Cloud Functions enabled (Blaze pay-as-you-go plan) for the OAuth broker
- A FlutterFlow project with at least one screen; familiarity with FlutterFlow App State is helpful
Step-by-step guide
Confirm you are using Time Doctor v2 and get your credentials
Before writing a single API call, confirm your API version. Time Doctor has two active APIs that share similar names but have different base URLs, authentication methods, and endpoint structures: - v1.1: base URL https://webapi.timedoctor.com/v1.1 or https://api2.timedoctor.com/v1.1 — authenticated with an API token from account settings. Simpler but an older API. - v2: base URL https://api2.timedoctor.com/api/1.0 — uses OAuth 2.0 Bearer tokens and is Time Doctor's current, recommended API for new integrations. This tutorial uses v2. Do NOT mix v1.1 URLs with v2 tokens or vice versa — the 401 errors you get look identical to wrong-credential errors and are extremely confusing to debug. To get v2 credentials, you need to register an OAuth application with Time Doctor. Contact Time Doctor support or check their developer documentation at timedoctor.com/integrations for the current process to register an app and receive a client ID and client secret. Some plans require that you contact support directly to enable API access. Also note your company ID — when you log into the Time Doctor web app, your URL includes a numeric company ID (https://2.timedoctor.com/123456). This number is required as a path segment in virtually every v2 API endpoint: /api/1.0/{companyId}/worklogs, /api/1.0/{companyId}/users, etc. Missing it returns a 404 that looks misleadingly like a wrong endpoint error.
Pro tip: Write the company ID on a sticky note during setup — it is easy to forget since it looks like a random number, and its absence causes 404 errors that give no indication the company ID is what is missing.
Expected result: You have confirmed you are using v2, have (or are in the process of obtaining) a client ID and client secret, and your company ID is noted.
Deploy a Firebase Cloud Function to broker OAuth 2.0 tokens
Time Doctor's OAuth 2.0 authorization-code flow requires a server-side redirect callback to receive the authorization code, and a client secret for the code-to-token exchange. Neither belongs in a Flutter app binary. A Firebase Cloud Function handles both safely. The Cloud Function does three things: (1) provides a redirect endpoint that Time Doctor calls after the user approves access, (2) exchanges the authorization code for access and refresh tokens and stores them in Firestore, and (3) provides a /token endpoint your FlutterFlow app calls to always get a valid, refreshed access token. Create a Node.js Firebase Cloud Function as shown in the code block. Deploy it and use the deployed function URL as the redirect URI when registering your Time Doctor OAuth application. After deploying, trigger the initial authorization by opening the Time Doctor OAuth URL in a browser: https://accounts2.timedoctor.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_FUNCTION_URL&response_type=code&scope=openid email profile After user approval, Time Doctor redirects to your Cloud Function with a code query parameter. The function exchanges the code for tokens and stores them in Firestore. From that point, your FlutterFlow app calls the /timeDoctorToken endpoint whenever it needs a fresh access token. Store the client ID, client secret, and redirect URI in Firebase Functions config (not in the code file): firebase functions:config:set timedoctor.client_id='...' timedoctor.client_secret='...' timedoctor.redirect_uri='...'
1// Firebase Cloud Function — Time Doctor OAuth broker (Node.js, index.js)2const functions = require('firebase-functions');3const admin = require('firebase-admin');4const axios = require('axios');5admin.initializeApp();6const db = admin.firestore();78const CLIENT_ID = functions.config().timedoctor.client_id;9const CLIENT_SECRET = functions.config().timedoctor.client_secret;10const REDIRECT_URI = functions.config().timedoctor.redirect_uri;11const TOKEN_URL = 'https://accounts2.timedoctor.com/oauth2/token';1213// Step 1: OAuth callback14exports.timeDoctorOAuth = functions.https.onRequest(async (req, res) => {15 const code = req.query.code;16 if (!code) { res.status(400).send('Missing authorization code'); return; }1718 try {19 const tokenRes = await axios.post(TOKEN_URL, new URLSearchParams({20 grant_type: 'authorization_code',21 client_id: CLIENT_ID,22 client_secret: CLIENT_SECRET,23 redirect_uri: REDIRECT_URI,24 code25 }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });2627 const { access_token, refresh_token, expires_in } = tokenRes.data;28 await db.collection('timeDoctorTokens').doc('default').set({29 access_token, refresh_token,30 expires_at: Date.now() + (expires_in * 1000)31 });32 res.send('Time Doctor authorization complete. You can close this tab.');33 } catch (e) {34 res.status(500).send('Token exchange failed: ' + e.message);35 }36});3738// Step 2: Token endpoint for FlutterFlow39exports.timeDoctorToken = functions.https.onRequest(async (req, res) => {40 res.set('Access-Control-Allow-Origin', '*');41 if (req.method === 'OPTIONS') {42 res.set('Access-Control-Allow-Methods', 'GET');43 res.set('Access-Control-Allow-Headers', 'Content-Type');44 res.status(204).send('');45 return;46 }47 const doc = await db.collection('timeDoctorTokens').doc('default').get();48 if (!doc.exists) { res.status(401).json({ error: 'Not authorized yet' }); return; }49 const { access_token, refresh_token, expires_at } = doc.data();5051 if (Date.now() > expires_at - 60000) {52 const r = await axios.post(TOKEN_URL, new URLSearchParams({53 grant_type: 'refresh_token',54 client_id: CLIENT_ID,55 client_secret: CLIENT_SECRET,56 refresh_token57 }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });58 await db.collection('timeDoctorTokens').doc('default').update({59 access_token: r.data.access_token,60 expires_at: Date.now() + (r.data.expires_in * 1000)61 });62 res.json({ token: r.data.access_token });63 } else {64 res.json({ token: access_token });65 }66});Pro tip: Time Doctor v2 uses https://accounts2.timedoctor.com/oauth2/ (note the '2' in accounts2) for OAuth endpoints — using accounts.timedoctor.com will fail with a 404. Verify the current OAuth URLs in Time Doctor's developer documentation.
Expected result: Two Cloud Functions are deployed: timeDoctorOAuth (handles the callback) and timeDoctorToken (returns a valid Bearer token). The initial OAuth flow has been triggered and tokens are stored in Firestore.
Create the FlutterFlow API Group for Time Doctor v2
Open your FlutterFlow project. In the left navigation, click API Calls → + Add → Create API Group. Name it 'Time Doctor'. In the Base URL field, enter: https://api2.timedoctor.com/api/1.0 Do not use the v1.1 URL — https://webapi.timedoctor.com/v1.1 — even if you see it referenced in older tutorials or sibling integrations. Commit to v2 from the start. In the Headers tab, add one header: - Authorization: Bearer {{access_token}} In the Variables tab of the API Group, add: access_token (String type). This group-level variable is populated from App State before any API call runs. Also add a group-level variable for company_id (String type). Since virtually all v2 endpoints have the structure /api/1.0/{companyId}/..., you will need this variable on every call. Adding it at the group level means you configure it once and all API Calls inherit it. To initialize the app: create a separate lightweight API Group pointing to your Firebase Cloud Functions URL. Add a single GET call 'Fetch Token' that calls /timeDoctorToken. Map the JSON path $.token to update the App State variable 'timeDoctorToken'. On app launch, trigger this Fetch Token call first, then set App State 'timeDoctorCompanyId' from the company ID you noted during setup. Every Time Doctor API Call then references both App State variables for the group-level variables.
1// API Group configuration reference:2// Group name: Time Doctor3// Base URL: https://api2.timedoctor.com/api/1.04// Group-level variables:5// access_token (String) — from App State6// company_id (String) — from App State7// Headers:8// Authorization: Bearer {{access_token}}9//10// Endpoint pattern: /{company_id}/worklogs11// (company_id is a path segment, not a query param)Pro tip: Set the company_id as a persistent App State variable (FlutterFlow's Persist option) so it survives app restarts. Users should not need to re-enter or re-fetch their company ID every time they open the app.
Expected result: A 'Time Doctor' API Group exists with the v2 base URL, Authorization Bearer variable header, and group-level variables for access_token and company_id.
Add GET API Calls for users and paginated worklogs
Inside the Time Doctor API Group, add your first API Call. Name it 'Get Users'. Endpoint: /{{company_id}}/users (the full URL becomes https://api2.timedoctor.com/api/1.0/COMPANY_ID/users). Method: GET. In the Response & Test tab, paste a sample Time Doctor users response. Create a Data Type 'TimeDoctorUser' with fields: id (String), full_name (String), email (String). Map JSON paths: $.users[*].id, $.users[*].full_name, $.users[*].email. Now add the worklogs call. Name it 'Get Worklogs'. Endpoint: /{{company_id}}/worklogs. Method: GET. Add query parameter variables: - start_date (String) — date in YYYY-MM-DD format - end_date (String) — date in YYYY-MM-DD format - user_ids (String) — optional, comma-separated user IDs to filter - limit (Integer) — pagination page size (e.g. 100) - offset (Integer) — pagination offset Time Doctor worklog responses can be large — a team of 20 people over a week generates thousands of time entries. Always include start_date, end_date, and limit/offset query parameters. Do not call worklogs without a date range, or the API may return months of data that takes seconds to load and approaches daily rate limits. Create a Data Type 'TimeDoctorWorklog' with fields: id (String), user_id (String), task_name (String), project_name (String), start_time (String), end_time (String), duration (Integer — seconds). Map the JSON paths from the response. Test the call in the test panel using yesterday's date as both start and end to get a small, fast response for verification.
1// GET /{company_id}/worklogs query parameters:2// start_date: 2026-07-01 (YYYY-MM-DD)3// end_date: 2026-07-074// user_ids: optional, e.g. 'abc123,def456'5// limit: 100 (max per page)6// offset: 0 (first page)7//8// JSON Paths for worklogs response:9// $.worklogs[*].id → TimeDoctorWorklog.id (String)10// $.worklogs[*].user_id → TimeDoctorWorklog.user_id (String)11// $.worklogs[*].task_name → TimeDoctorWorklog.task_name (String)12// $.worklogs[*].project_name → TimeDoctorWorklog.project_name (String)13// $.worklogs[*].start_time → TimeDoctorWorklog.start_time (String)14// $.worklogs[*].length → TimeDoctorWorklog.duration (Integer, seconds)Pro tip: Verify the exact JSON response structure in the test panel — Time Doctor's API response keys differ slightly between v1.1 and v2. The v2 worklogs response uses 'worklogs' as the top-level array key, not 'tasks' or 'records' as some older documentation suggests.
Expected result: Get Users and Get Worklogs API Calls are in the Time Doctor group, both returning 200 OK in the test panel. Data Types for users and worklogs are created with JSON Path mappings.
Build the dashboard: user list, date picker, and worklogs grouped by person
On your main dashboard screen, add a DateRangePicker widget (or two separate DatePicker widgets for start/end) at the top. Store the selected dates in App State variables (start_date and end_date as Strings in YYYY-MM-DD format). Add a Button labeled 'Load Worklogs'. When the button is tapped, trigger the Action Flow: first call Get Worklogs with the current App State dates, company_id, and access_token. Store the response as a List of TimeDoctorWorklog in App State. Then call Get Users if you have not already and store as a List of TimeDoctorUser in App State. For the main display, add a ListView widget bound to the users list. For each user row, show the user's full_name and calculate their total duration from the worklogs (you can use a Custom Function in FlutterFlow to filter the worklogs list by user_id and sum the duration integers). Display total hours as formatted text (e.g., '6h 24m'). Add a detail screen that opens when the user taps a team member's row — this screen receives the user_id and full_name as parameters and shows a ListView of that person's individual worklogs for the selected date range, filtered from the App State worklogs list. For the chart view, FlutterFlow's built-in Bar Chart or Line Chart widget can display hours per user or hours per day. Bind the chart data to the aggregated totals you calculate with Custom Functions. Keep polling modest. Do not auto-refresh the worklog data more than once every few minutes. Time Doctor's API has per-day rate quotas that can be exhausted if a dashboard calls the worklogs endpoint on every screen render or with aggressive background polling.
Pro tip: Custom Functions in FlutterFlow (not Custom Actions) run in the widget build cycle and are ideal for aggregation logic like summing worklogs by user — they do not require a device build and work in Run Mode for testing.
Expected result: The dashboard shows a team member list with total hours for the selected date range. Tapping a user opens a detail screen with their individual time entries. A bar chart displays hours per person.
Handle token refresh, version errors, and rate-limit quotas
Time Doctor access tokens expire, and the Firebase Cloud Function's /timeDoctorToken endpoint automatically refreshes them before expiry. But your FlutterFlow app needs to handle the case where the cached App State token becomes stale mid-session — for example, if the user keeps the app open for an extended period. In every API Call action in the Action Flow Editor, add a conditional after the call: if the response HTTP status code is 401, trigger the Fetch Token API Call (to your Firebase Cloud Function), update the App State access_token, and retry the original call once. This pattern covers the vast majority of mid-session expiry scenarios. For 404 errors: the most common cause is using the wrong v1.1 base URL or omitting the company ID path segment. If you see 404 on a call that should work, open the API Call's test panel and check the full URL it is constructing — confirm it matches https://api2.timedoctor.com/api/1.0/YOUR_COMPANY_ID/endpoint_name. For rate-limit errors: Time Doctor v2 has per-day quotas on some endpoints. A dashboard that polls worklogs every 30 seconds for a 20-person team can hit these limits within hours. Implement a 'last loaded' App State timestamp and only allow a refresh if more than 5 minutes have passed since the last fetch. Show a 'Data last updated: X minutes ago' label to users so they understand why the refresh button is temporarily disabled. If the OAuth setup feels too complex for your current sprint, RapidDev's team sets up FlutterFlow OAuth integrations like Time Doctor regularly. Book a free scoping call at rapidevelopers.com/contact to discuss options.
1// Action Flow pattern for Time Doctor API calls:2// 1. Call API: Get Worklogs (with current access_token from App State)3// 2. Condition: API Response Code == 4014// TRUE:5// a. Call API: Fetch Token (Firebase /timeDoctorToken)6// b. Update App State: timeDoctorToken = $.token7// c. Retry: Call API: Get Worklogs8// FALSE:9// a. Condition: API Response Code == 40410// → Show Snack Bar: 'API error — check company ID'11// b. Condition: Response Code == 20012// → Update App State: worklogs = response list13// → Update App State: lastLoaded = current timestampPro tip: Add a 5-minute cooldown on the refresh button using FlutterFlow's Custom Functions to compare the current time with the lastLoaded App State timestamp — this prevents users from accidentally triggering rate-limit-exhausting rapid refreshes.
Expected result: The app handles expired tokens transparently via a retry pattern. Version-mismatch 404 errors are caught and displayed helpfully. The dashboard enforces a minimum refresh interval to protect against daily rate-limit exhaustion.
Common use cases
Remote team daily worklog dashboard
A company managing a distributed team of contractors builds a FlutterFlow iOS app for team leads. The app shows today's worklogs broken down by team member — hours tracked, current task, and last activity timestamp. The Time Doctor API feeds the data, the Firebase Cloud Function brokers the token, and the FlutterFlow dashboard refreshes when the manager pulls down to reload.
Build a FlutterFlow dashboard screen that shows all team members' time tracked today, sorted by hours descending, with each person's current task and an active/idle status indicator.
Copy this prompt to try it in FlutterFlow
Weekly utilization report viewer
A consulting firm builds a FlutterFlow Android tablet app for account managers to review how their billable team spent time last week. The app calls Time Doctor's worklog endpoint with a date range, groups hours by project, and renders a bar chart using FlutterFlow's Chart widget. The Firebase Cloud Function handles token refresh so the app stays authenticated week after week.
Build a weekly report screen with a bar chart showing hours per project for the selected date range, pulling data from Time Doctor's worklog API.
Copy this prompt to try it in FlutterFlow
Personal time tracking summary app
A freelancer uses FlutterFlow to build a personal productivity app that connects to their Time Doctor account and shows their own daily and weekly time summaries by client and project. Because this is a single-user app, the OAuth token is fetched once and stored — the Firebase Cloud Function refreshes it automatically.
Build a personal productivity screen that shows my Time Doctor time entries for today, grouped by project, with a total hours counter at the top.
Copy this prompt to try it in FlutterFlow
Troubleshooting
401 Unauthorized on every v2 API call even after OAuth completes
Cause: The most common cause is a v1.1 vs v2 version mismatch. If your Cloud Function exchanges tokens against the v2 OAuth URL (accounts2.timedoctor.com) but your API Group targets the v1.1 base URL (webapi.timedoctor.com/v1.1), or vice versa, the tokens are valid for the wrong API version.
Solution: Confirm your API Group base URL is exactly https://api2.timedoctor.com/api/1.0 (v2). Confirm your Cloud Function uses the v2 token URL (https://accounts2.timedoctor.com/oauth2/token). Both sides must be v2. If you previously tried v1.1, delete the old API Group and create a fresh one to avoid cached configuration.
404 Not Found on worklog or user endpoints
Cause: The company ID path segment is missing from the endpoint. Time Doctor v2 requires every endpoint to include /{companyId}/ as a path segment (e.g., /api/1.0/123456/worklogs), not as a query parameter.
Solution: Open the API Call in the API Calls panel and verify the endpoint field reads /{{company_id}}/worklogs (with the variable for the company ID). In the test panel, enter your actual company ID as the variable value and check that the test URL includes the number in the path. If you left the company ID as a hardcoded number, wrap it in {{}} as a variable so it is cleanly managed.
Worklogs response is very slow or times out
Cause: The request is missing date-range query parameters, so Time Doctor is attempting to return all historical worklogs for the company — potentially months of data.
Solution: Always include start_date and end_date query parameters when calling the worklogs endpoint. Use narrow ranges (e.g., today, or current week) for the default view, and allow users to expand the range only when explicitly requested. Add limit and offset pagination parameters and only fetch 100 records per call.
API returns correct data in test panel but nothing in the FlutterFlow app
Cause: The access_token App State variable is empty or not being passed to the API Group variable when the call fires. The API Call returns 401 silently without showing an error in the widget binding.
Solution: Add a debug Snack Bar action before the API call to display the current value of the App State access_token variable. If it is empty, the token fetch from Firebase is not completing before the API call fires. Ensure you use a 'Wait' or sequential action chain (not parallel) so the token fetch completes before the worklogs call is triggered.
Best practices
- Always use Time Doctor v2 (https://api2.timedoctor.com/api/1.0) for new integrations — never mix v1.1 URLs with v2 tokens.
- Keep the OAuth client secret exclusively in Firebase Cloud Function environment config — never in Dart code, App State, or API Call headers.
- Always include start_date and end_date query parameters on worklog calls — open-ended queries can return months of data and quickly exhaust daily rate quotas.
- Store the company ID in persisted App State so it survives app restarts and does not need to be re-fetched every session.
- Implement a minimum refresh interval (e.g., 5 minutes) for dashboard data to prevent rate-limit exhaustion — Time Doctor's per-day quotas are limited and easy to deplete with aggressive polling.
- Handle 401 errors in your Action Flow with a token-refresh-and-retry pattern rather than sending users back to a login screen — token expiry is routine and should be invisible to users.
- Test on a real device or APK build when validating the OAuth flow — FlutterFlow's Run Mode browser preview may handle the redirect callback differently than a native app.
Alternatives
Everhour uses a single X-Api-Key header with no OAuth required, making the FlutterFlow integration significantly simpler — the right choice if your team does not need Time Doctor's activity monitoring and screenshot features.
Teamwork combines project management with built-in time logging and uses a simpler API-key authentication model, making it easier to build in FlutterFlow while still covering time-tracking needs.
ClickUp's time tracking API is part of a broader project management suite and uses personal API tokens (simpler than OAuth), making it a less complex alternative if your team already uses ClickUp for task management.
Frequently asked questions
Can I use the v1.1 API instead of v2 to avoid the OAuth complexity?
The v1.1 API (https://webapi.timedoctor.com/v1.1) uses a simpler API token from account settings, which is technically easier to implement in FlutterFlow. However, v2 is Time Doctor's current recommended API for new integrations, and v1.1 may have reduced support or missing endpoints over time. If you need a simpler first implementation, v1.1 with an API token is viable — but plan to migrate to v2 for production.
Does Time Doctor offer a free plan with API access?
Time Doctor is a paid product starting from approximately $7/user/month for the Basic plan (verify current pricing at timedoctor.com). API access is available on paid plans. There is no free plan that includes API access, though Time Doctor offers trial periods. Confirm API access is included on your specific plan tier before starting the integration.
What does the company ID look like and where do I find it?
The Time Doctor company ID is a numeric identifier that appears in your dashboard URL after logging in — typically at https://2.timedoctor.com/123456. The six-or-more-digit number after the domain is your company ID. It is also sometimes called 'account ID' in Time Doctor's documentation. This number is required as a path segment in every v2 API endpoint.
Can my FlutterFlow app track time directly to Time Doctor (start/stop timer)?
Yes, Time Doctor v2 has endpoints for creating time entries programmatically, which lets your FlutterFlow app function as a timer that logs to Time Doctor. POST to /api/1.0/{companyId}/worklogs with a start_time, end_time, and task reference. However, active monitoring features (screenshots, app tracking) only work through Time Doctor's desktop client — you cannot trigger those from a mobile Flutter app.
Do I need a separate login in FlutterFlow for each team member, or one shared account?
For a team dashboard showing all members' data, a single admin OAuth token (stored server-side in Firebase) is the simplest approach — your FlutterFlow app reads all users' worklogs through one authenticated session. For a per-user app where each person sees only their own data, you would need each user to go through the OAuth flow individually, which requires per-user token storage in Firestore. The single admin token pattern is far simpler to implement and suitable for most internal dashboard use cases.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation