To integrate Harvest with V0 by Vercel, create a Next.js API route that authenticates via OAuth2 and fetches time entries, projects, and invoices from the Harvest API. V0 generates the time tracking dashboard UI; you add your Harvest OAuth credentials to Vercel environment variables and deploy. The result is a custom time reporting and invoicing dashboard built on top of Harvest data.
Build Custom Time Tracking Dashboards and Reports on Top of Harvest
Harvest provides a comprehensive REST API (v2) with endpoints covering every aspect of its time tracking and invoicing model. V0 developers use this to build dashboards and reporting tools that give agencies and freelancers deeper insights than Harvest's built-in reports. Common use cases include weekly capacity dashboards showing team availability, client portals displaying their project's time and budget status, and automated invoice tracking boards.
V0 generates the React components for these dashboards — timesheet tables, budget progress bars, billable/non-billable donut charts, and project health indicators. Your Next.js API routes proxy calls to the Harvest API, handling authentication and transforming responses. The Harvest API requires two authentication headers on every request: an Authorization header with your bearer token and a Harvest-Account-ID header with your account ID.
For most internal tools, Harvest Personal Access Tokens (PATs) are the fastest setup path — no OAuth flow required. For multi-tenant apps where each user connects their own Harvest account, use OAuth2. Both authentication methods work with the same Harvest API endpoints.
Integration method
Harvest connects to V0-generated Next.js apps through server-side API routes that authenticate using OAuth2 or Personal Access Tokens and call the Harvest REST API. The credentials stay in Vercel environment variables. Your app fetches time entries, project budgets, and invoice data from Harvest to build custom reporting dashboards that go beyond Harvest's built-in reports.
Prerequisites
- A V0 account at v0.dev with a Next.js project created
- A Harvest account at getharvest.com (Harvest charges a subscription — free trial available)
- A Harvest Personal Access Token from Settings → Developers in the Harvest web app
- Your Harvest Account ID (visible in the URL when logged in to Harvest: app.harvestapp.com/time/accounts/YOUR_ID)
- A Vercel account connected to your V0 project for deployment
Step-by-step guide
Generate the Harvest Dashboard UI with V0
Generate the Harvest Dashboard UI with V0
Open your V0 project and use the chat to generate the time tracking dashboard interface. Be descriptive about the data you want to display — Harvest's API is rich with time entry data (date, hours, notes, project, task, person), project data (budget, spent hours, client), and invoice data (amounts, status, due dates). For a team utilization view, describe a table with team member names in rows and metrics in columns. For a project view, describe progress bars and budget comparison. For an invoice view, describe a kanban board or filterable table with status badges. The more specific you are about the layout, the less iteration you will need. Ask V0 to wire up the components to your planned API routes (/api/harvest/time-entries, /api/harvest/projects, /api/harvest/invoices). Specify the query parameters you want to support — date ranges, user filters, project ID. V0 will generate React components with useEffect hooks that fetch from these routes and display the results. Use Recharts for any charts since it comes pre-installed with V0's default stack.
Create a time tracking dashboard with three sections: (1) a summary row showing total hours this week, billable hours, and non-billable hours as stat cards; (2) a team utilization table with columns for name, hours, billable %, and a mini progress bar; (3) a list of the top 5 projects by hours this week. Fetch all data from /api/harvest/summary?week=current. Add a week navigation control. Use shadcn/ui components.
Paste this in V0 chat
Pro tip: Ask V0 to include a loading state using shadcn/ui Skeleton components while Harvest data is fetching — API calls to Harvest typically take 200-500ms and the dashboard looks broken without loading indicators.
Expected result: V0 generates a time tracking dashboard with stat cards, tables, and charts using mock data. The components are wired to API routes that do not exist yet.
Create the Harvest Time Entries API Route
Create the Harvest Time Entries API Route
Create a new file at app/api/harvest/route.ts. This is your server-side proxy to the Harvest API. The Harvest API v2 requires two headers on every request: Authorization: Bearer {token} (your Personal Access Token or OAuth2 access token) and Harvest-Account-ID: {account_id} (your numeric account ID). Both must be present for every API call. The time entries endpoint at https://api.harvestapp.com/v2/time_entries supports rich filtering: you can filter by project_id, user_id, from date (YYYY-MM-DD), to date, and whether entries are billed. The API returns paginated results with a next_page cursor — handle pagination if you need more than the default 100 entries per page. The response includes each time entry's ID, date, hours, notes, billable flag, project object, task object, and user object. Your API route can transform this data before returning it — for example, aggregating hours by user for a utilization view or grouping by project for a budget view. This server-side transformation reduces the amount of data sent to the browser and keeps your components simple.
1import { NextRequest, NextResponse } from 'next/server';23const HARVEST_BASE_URL = 'https://api.harvestapp.com/v2';45export async function GET(request: NextRequest) {6 const { searchParams } = new URL(request.url);7 const from = searchParams.get('from');8 const to = searchParams.get('to');9 const projectId = searchParams.get('project_id');10 const userId = searchParams.get('user_id');1112 const token = process.env.HARVEST_ACCESS_TOKEN;13 const accountId = process.env.HARVEST_ACCOUNT_ID;1415 if (!token || !accountId) {16 return NextResponse.json(17 { error: 'Harvest credentials not configured' },18 { status: 500 }19 );20 }2122 const params = new URLSearchParams();23 if (from) params.set('from', from);24 if (to) params.set('to', to);25 if (projectId) params.set('project_id', projectId);26 if (userId) params.set('user_id', userId);27 params.set('per_page', '100');2829 try {30 const response = await fetch(31 `${HARVEST_BASE_URL}/time_entries?${params.toString()}`,32 {33 headers: {34 Authorization: `Bearer ${token}`,35 'Harvest-Account-ID': accountId,36 'User-Agent': 'MyApp (myapp@example.com)',37 'Content-Type': 'application/json',38 },39 }40 );4142 if (!response.ok) {43 const error = await response.json();44 return NextResponse.json(45 { error: error.message || 'Harvest API error' },46 { status: response.status }47 );48 }4950 const data = await response.json();51 return NextResponse.json(data);52 } catch (error) {53 console.error('Harvest API error:', error);54 return NextResponse.json(55 { error: 'Failed to fetch time entries' },56 { status: 500 }57 );58 }59}Pro tip: The Harvest API requires a User-Agent header with your app name and contact email. Harvest uses this to identify your integration and contact you about any issues — include a real email address.
Expected result: Calling /api/harvest?from=2026-03-01&to=2026-03-31 returns paginated time entries from Harvest after credentials are configured in the next step.
Add Harvest Credentials to Vercel Environment Variables
Add Harvest Credentials to Vercel Environment Variables
Your Harvest Personal Access Token and Account ID must be stored as Vercel environment variables. Go to Vercel Dashboard → Settings → Environment Variables and add two variables. To get your credentials: log in to Harvest at app.harvestapp.com, click your profile icon in the top-right corner, and select Developers. On the Developers page, scroll to the Personal Access Tokens section and click Create New Personal Access Token. Give it a name describing your integration (e.g., 'V0 Dashboard App') and copy the token — you can only see it once, so copy it immediately. For your Account ID: it appears in the URL when you are in Harvest. Navigate to any page in Harvest and look at the URL — it will contain app.harvestapp.com/time/accounts/12345678 where 12345678 is your Account ID. You can also find it in the Developers page. In Vercel, add HARVEST_ACCESS_TOKEN with your PAT value and HARVEST_ACCOUNT_ID with your numeric account ID. Both should be available in All Environments. After adding the variables, redeploy your project to make them available to your API routes.
Pro tip: For team projects where multiple team members' time data needs to be accessed, you may need a Harvest account with admin access or use OAuth2 so each team member authorizes the app. A single PAT only accesses data visible to that specific Harvest account.
Expected result: HARVEST_ACCESS_TOKEN and HARVEST_ACCOUNT_ID are set in Vercel. API calls to /api/harvest return real time entries from your Harvest account after redeployment.
Add Projects and Invoices Endpoints
Add Projects and Invoices Endpoints
Expand your Harvest integration by adding additional API routes for projects and invoices, which are commonly needed alongside time entries for a complete dashboard. Create app/api/harvest/projects/route.ts to fetch project data including name, client, budget hours, budget spent, and active status. The Harvest projects endpoint at /v2/projects supports filtering by client_id and is_active. The project report endpoint at /v2/reports/time/projects returns pre-aggregated data with hours and costs by project — useful for dashboard summaries without needing to aggregate time entries yourself. Create app/api/harvest/invoices/route.ts to fetch invoice data including client, total amount, due date, issue date, and payment status (draft, open, paid, overdue). The invoices endpoint at /v2/invoices supports filtering by status, client_id, and date ranges. This data powers invoice pipeline views, accounts receivable summaries, and cash flow forecasts. For each new endpoint, follow the same pattern: read credentials from environment variables, call the Harvest API with both required headers, handle errors, and return the data. For complex integrations combining multiple Harvest data sources into a single dashboard API response, RapidDev's team can help design the data aggregation layer.
Add an invoices section to the dashboard showing a summary of open invoices: total outstanding amount, count of overdue invoices, and a table of recent invoices with client name, amount, due date, and a colored status badge (Paid=green, Open=blue, Overdue=red, Draft=gray). Fetch from /api/harvest/invoices. Add below the time tracking section.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23const HARVEST_BASE_URL = 'https://api.harvestapp.com/v2';45export async function GET(request: NextRequest) {6 const { searchParams } = new URL(request.url);7 const state = searchParams.get('state'); // open, paid, overdue, draft8 const clientId = searchParams.get('client_id');910 const token = process.env.HARVEST_ACCESS_TOKEN;11 const accountId = process.env.HARVEST_ACCOUNT_ID;1213 if (!token || !accountId) {14 return NextResponse.json(15 { error: 'Harvest credentials not configured' },16 { status: 500 }17 );18 }1920 const params = new URLSearchParams();21 if (state) params.set('state', state);22 if (clientId) params.set('client_id', clientId);23 params.set('per_page', '50');2425 try {26 const response = await fetch(27 `${HARVEST_BASE_URL}/invoices?${params.toString()}`,28 {29 headers: {30 Authorization: `Bearer ${token}`,31 'Harvest-Account-ID': accountId,32 'User-Agent': 'MyApp (myapp@example.com)',33 },34 }35 );3637 if (!response.ok) {38 return NextResponse.json(39 { error: 'Failed to fetch invoices' },40 { status: response.status }41 );42 }4344 const data = await response.json();45 return NextResponse.json(data);46 } catch (error) {47 return NextResponse.json(48 { error: 'Failed to fetch invoice data' },49 { status: 500 }50 );51 }52}Pro tip: Harvest's /v2/reports/time/projects and /v2/reports/time/team endpoints return pre-aggregated data with totals, saving you from aggregating individual time entries in your app code.
Expected result: The invoices endpoint returns invoice data from Harvest. The dashboard now shows both time tracking and invoice pipeline sections with real data.
Common use cases
Team Capacity and Billable Hours Dashboard
Build an internal dashboard showing each team member's total hours, billable vs non-billable ratio, and utilization rate for the current week or month. Pull time entries from Harvest filtered by date range and user, aggregate by person, and display as a sortable table with visual indicators.
Create a team utilization dashboard with a table showing each team member's name, total hours tracked this week, billable hours, non-billable hours, and utilization percentage (billable/target hours). Fetch data from /api/harvest/time-entries?week=current. Add a week picker to navigate to previous weeks. Color-code utilization: green above 80%, yellow 60-80%, red below 60%.
Copy this prompt to try it in V0
Project Budget Burn-Down Report
Display a real-time budget report for a specific project showing total budgeted hours, hours spent to date, percentage consumed, and a forecast of when the budget will be exhausted. This helps project managers proactively communicate scope changes to clients.
Build a project budget report page that fetches project data from /api/harvest/project-budget and shows: total budget hours, hours used, remaining hours, and a linear progress bar. Display a 'Forecast complete date' calculated from current burn rate. Show a breakdown table of hours by task category. Add a warning banner if over 80% of budget is used.
Copy this prompt to try it in V0
Client Invoice Status Tracker
Create a client-facing or internal invoice pipeline view showing all invoices with their status (draft, sent, paid, overdue), due dates, and total amounts. Sales teams and bookkeepers can quickly see which invoices need follow-up without logging into Harvest.
Create an invoice tracker page that fetches invoices from /api/harvest/invoices. Display as a kanban-style board with columns: Draft, Sent, Partial Payment, Paid, Overdue. Each card shows client name, invoice number, total amount, and due date. Add a total at the bottom of each column. Use shadcn/ui Card components and color-coded status badges.
Copy this prompt to try it in V0
Troubleshooting
401 Unauthorized — 'Invalid access token' from Harvest API
Cause: The HARVEST_ACCESS_TOKEN environment variable is missing or the token was deleted/expired in Harvest. Personal Access Tokens in Harvest do not expire, but they can be deleted from the Developers page.
Solution: Log in to Harvest, go to your profile → Developers, and verify your Personal Access Token still exists. If it was deleted, create a new one, copy it immediately (it is only shown once), and update the HARVEST_ACCESS_TOKEN variable in Vercel. Redeploy after updating.
403 Forbidden — API returns 'You do not have permission to access this resource'
Cause: The Harvest account associated with the Personal Access Token does not have access to the requested resource. For example, trying to access another team member's time entries without admin permissions.
Solution: Use a Harvest account with appropriate permissions for the data you need. For team-wide reports, the account must have the 'Manager' role in Harvest. Check Harvest's access control documentation for which roles can access which API endpoints.
Empty time entries returned even though entries exist in Harvest
Cause: The date filters (from, to) are in the wrong format, or the time zone difference is pushing entries to a different day. Harvest uses YYYY-MM-DD date format in the API.
Solution: Verify the from and to query parameters are in YYYY-MM-DD format. If your app is in a different timezone than UTC, adjust the dates accordingly. Check the actual API request in Vercel Function Logs to confirm the date parameters are being sent correctly.
1// Correct date format for Harvest API2const today = new Date().toISOString().split('T')[0]; // '2026-03-31'3const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];Harvest API returns 429 Too Many Requests
Cause: The Harvest API has rate limits: 100 requests per 15 seconds per account. Building a dashboard that makes many parallel API calls or polling frequently can exceed this limit.
Solution: Implement caching for Harvest data responses — cache time entries for 5 minutes and project data for 15 minutes using Next.js fetch cache options. Consolidate multiple API calls into a single server-side fetch that aggregates data before returning to the frontend. Avoid polling the Harvest API more than once per minute.
1// Add caching to fetch calls2const response = await fetch(harvestUrl, {3 headers: harvestHeaders,4 next: { revalidate: 300 }, // Cache for 5 minutes5});Best practices
- Always include both required Harvest headers on every request: Authorization: Bearer {token} and Harvest-Account-ID: {account_id} — missing either header returns a 401 regardless of token validity.
- Use Harvest's pre-aggregated report endpoints (/v2/reports/time/projects, /v2/reports/time/team) for dashboard summaries instead of fetching all time entries and aggregating in your app.
- Cache Harvest API responses in Next.js using fetch cache options or a caching layer — Harvest has rate limits (100 req/15s) and dashboard data does not change second-by-second.
- Store only a PAT in environment variables for single-account dashboards — implementing full OAuth2 adds complexity that is only necessary when different users each connect their own Harvest account.
- Handle Harvest API pagination — the default page size is 100 entries and large date ranges or active teams may have more. Check response.total_pages and fetch additional pages if needed.
- Include the User-Agent header with your app name and contact email as required by the Harvest API terms of service — this helps Harvest identify and contact you about integration issues.
- Use from/to date filters on all time entry requests rather than fetching all entries — unbounded queries can be very slow on accounts with years of time tracking data.
Alternatives
Clockify offers a free tier with a very similar API structure, making it a good alternative if you want to avoid Harvest's subscription cost for time tracking integrations.
Everhour integrates directly with Asana, Trello, and GitHub, making it better if your time tracking needs to link to project management tools your team already uses.
Teamwork combines project management and time tracking in one platform, making it a stronger alternative if you need both features rather than using Harvest alongside a separate PM tool.
Frequently asked questions
What is the difference between a Harvest Personal Access Token and OAuth2?
A Personal Access Token (PAT) is tied to your own Harvest account and works immediately without an OAuth flow. It is ideal for internal dashboards and single-account integrations. OAuth2 is needed when multiple different users each need to authorize your app to access their individual Harvest accounts — the full authorization code flow is required to get separate tokens for each user.
Can I access all of my team's time entries with one API token?
Yes, if the Harvest account associated with your Personal Access Token has Manager or Admin permissions. Manager-level accounts can see all time entries across the whole account. If your PAT is for a regular contributor account, you will only see that user's own time entries and projects they are assigned to.
Does Harvest have webhooks for real-time updates?
Yes, Harvest supports webhooks that fire on events like time entry creation/update, invoice payment, and project creation. You can configure webhooks in Harvest Dashboard → Settings → Integrations → Webhooks. This enables real-time dashboard updates without polling the API. Use a POST handler in your Next.js API route to receive and process webhook payloads.
How does the Harvest API rate limit work?
Harvest allows up to 100 API requests per 15-second window per account. If you exceed this, the API returns a 429 Too Many Requests response with a Retry-After header indicating when you can retry. Dashboard applications that aggregate multiple data sources should cache responses and avoid parallel fan-out requests that collectively hit the rate limit.
What is the difference between Harvest and Clockify?
Harvest is a paid professional tool (starting around $10.80/user/month) with built-in invoicing, expense tracking, and deeper project budgeting. Clockify is free for unlimited users with a simpler feature set focused purely on time tracking. For apps that need invoicing data alongside time tracking, Harvest's API provides significantly more financial data.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation