To integrate FreshBooks with V0 by Vercel, generate an invoicing or expense dashboard with V0, create a Next.js API route using OAuth2 to call the FreshBooks API, store tokens in Vercel environment variables, and deploy. Your app can create invoices, track expenses, and manage clients without exposing credentials to the browser.
Build Invoicing Dashboards and Accounting Tools with FreshBooks and V0
FreshBooks is the accounting platform of choice for millions of freelancers and small agencies who need professional invoicing without the complexity of enterprise accounting software. Its API makes it possible to build custom client portals, automated invoicing workflows, and business analytics dashboards on top of your FreshBooks account data. For founders building apps with V0, integrating FreshBooks means your app can create invoices programmatically, track billable hours, and give clients a branded view of their account — all without them ever needing to log into FreshBooks directly.
FreshBooks uses OAuth2 for authentication, which means your app requests permission from the FreshBooks account holder and receives access and refresh tokens that expire and need to be renewed. For apps built by freelancers using their own FreshBooks account, the simplest approach is to generate a long-lived refresh token once during setup and store it in Vercel environment variables. Your API routes use the refresh token to obtain fresh access tokens as needed, transparently to the end user. This avoids building a full OAuth2 callback flow for single-account integrations.
The FreshBooks API is organized around business accounts — each FreshBooks user has an account ID that is included in every API request URL. Common operations include creating invoices (POST /accounting/account/{accountId}/invoices/invoices), listing clients (GET /accounting/account/{accountId}/users/clients), recording expenses (POST /accounting/account/{accountId}/expenses/expenses), and fetching time entries. V0 excels at generating the data tables, invoice preview cards, and form interfaces that make these API operations feel polished and user-friendly.
Integration method
FreshBooks integrates with V0-generated Next.js apps through server-side API routes that call the FreshBooks REST API using OAuth2 access tokens. Your client credentials and access tokens are stored as server-only Vercel environment variables and never reach the browser. V0 generates the invoicing UI and accounting dashboards; Next.js API routes handle the secure communication with FreshBooks for invoice creation, expense tracking, and client management. This architecture keeps financial data secure while giving users a seamless in-app accounting experience.
Prerequisites
- A FreshBooks account at freshbooks.com — start with a free trial if you do not have one
- A FreshBooks app created at my.freshbooks.com/dashboard → API developer portal — note your Client ID and Client Secret
- Your FreshBooks account ID — visible in the URL when logged in as my.freshbooks.com/account/{accountId}/..., or from the /auth/api/v1/users/me endpoint
- An OAuth2 access token and refresh token obtained by completing the FreshBooks OAuth flow at least once — see FreshBooks API docs at freshbooks.com/api
- A V0 account at v0.dev and a Vercel account for deployment
Step-by-step guide
Generate the Invoicing UI with V0
Generate the Invoicing UI with V0
Open V0 at v0.dev and describe the accounting or invoicing interface you want to build. FreshBooks integrations typically center on one of three surfaces: an invoice creator form, an expense tracker, or a financial reporting dashboard. Start by building the primary interface your users will interact with most. Be specific about which data fields map to FreshBooks API fields — for invoices, the key fields are the client (customerid), lines array (each with a description, amount, and quantity), and the invoice status. For expense tracking, you need category, amount, date, and vendor information. Tell V0 the exact API endpoint paths your component should call (/api/freshbooks/invoices, /api/freshbooks/expenses) and what the request body structure should look like. Also describe the success and error states explicitly — for invoice creation, a success state might show the new invoice number and a button to view it in FreshBooks. V0 generates responsive React components with Tailwind CSS and proper loading states automatically. After generating, use V0's Git panel to push to GitHub and trigger a Vercel preview deployment.
Build an invoice creation form for a freelancer dashboard. Include: a client search input (dropdown that shows client names, POSTing search to /api/freshbooks/clients/search), a project/description field, and a dynamic line items section with Add Line Item button where each row has description, quantity, and rate inputs. Show auto-calculated totals (subtotal, tax at adjustable percentage, grand total). Include Due Date picker, Notes textarea, and two buttons: Save Draft and Send Invoice. POST to /api/freshbooks/invoices with the complete invoice data. Show the created invoice number in a success card after creation.
Paste this in V0 chat
Pro tip: Ask V0 to generate the line items section with dynamic add/remove functionality — this is the most complex part of an invoice form and V0 handles it well with React state management when you describe it explicitly.
Expected result: A professional invoice creation form renders in V0's preview with client selection, dynamic line items, automatic total calculation, and success/error state handling when posting to /api/freshbooks/invoices.
Set Up OAuth2 and Create the FreshBooks API Route
Set Up OAuth2 and Create the FreshBooks API Route
FreshBooks uses OAuth2, so your API routes need a valid access token to make requests. For a single-account integration (common for freelancers using their own FreshBooks account), the workflow is: complete the OAuth flow once in a browser to get an access token and refresh token, store both in Vercel environment variables, then use the refresh token to silently obtain new access tokens when they expire (FreshBooks access tokens expire after 12 hours). To complete the initial OAuth flow, configure a redirect URI in your FreshBooks app settings pointing to a temporary route like /api/freshbooks/callback, then visit the authorization URL: https://my.freshbooks.com/service/auth/oauth/authorize?client_id={YOUR_CLIENT_ID}&response_type=code&redirect_uri={YOUR_REDIRECT_URI}. After authorizing, FreshBooks redirects to your callback URL with a code parameter — exchange this code for tokens using a POST to https://api.freshbooks.com/auth/oauth/token. Save the returned access_token and refresh_token. The core FreshBooks API base URL is https://api.freshbooks.com and all business endpoints include your account ID in the path. Create an invoice by POSTing to /accounting/account/{accountId}/invoices/invoices with an invoice object that includes customerid, lines, and due_offset_days. The response wraps data in a response.result.invoice object.
1// app/api/freshbooks/invoices/route.ts2import { NextRequest, NextResponse } from 'next/server';34const FB_API_BASE = 'https://api.freshbooks.com';56async function getAccessToken(): Promise<string> {7 // Refresh the access token using the stored refresh token8 const response = await fetch(`${FB_API_BASE}/auth/oauth/token`, {9 method: 'POST',10 headers: { 'Content-Type': 'application/json' },11 body: JSON.stringify({12 grant_type: 'refresh_token',13 client_id: process.env.FRESHBOOKS_CLIENT_ID,14 client_secret: process.env.FRESHBOOKS_CLIENT_SECRET,15 refresh_token: process.env.FRESHBOOKS_REFRESH_TOKEN,16 }),17 });1819 if (!response.ok) {20 throw new Error('Failed to refresh FreshBooks access token');21 }2223 const data = await response.json();24 return data.access_token;25}2627interface LineItem {28 description: string;29 quantity: number;30 rate: number;31}3233interface CreateInvoiceRequest {34 clientId: string;35 lines: LineItem[];36 notes?: string;37 dueDays?: number;38}3940export async function POST(request: NextRequest) {41 const accountId = process.env.FRESHBOOKS_ACCOUNT_ID;4243 if (!accountId || !process.env.FRESHBOOKS_CLIENT_ID) {44 return NextResponse.json(45 { error: 'FreshBooks is not configured' },46 { status: 500 }47 );48 }4950 let body: CreateInvoiceRequest;51 try {52 body = await request.json();53 } catch {54 return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });55 }5657 const { clientId, lines, notes, dueDays = 30 } = body;5859 if (!clientId || !lines?.length) {60 return NextResponse.json(61 { error: 'clientId and at least one line item are required' },62 { status: 400 }63 );64 }6566 try {67 const accessToken = await getAccessToken();6869 const invoiceLines = lines.map((line) => ({70 description: { name: line.description },71 amount: { amount: (line.quantity * line.rate).toFixed(2), code: 'USD' },72 qty: line.quantity,73 unit_cost: { amount: line.rate.toFixed(2), code: 'USD' },74 }));7576 const payload = {77 invoice: {78 customerid: clientId,79 lines: invoiceLines,80 notes: notes || '',81 due_offset_days: dueDays,82 status: 1, // 1 = draft, 2 = sent83 },84 };8586 const response = await fetch(87 `${FB_API_BASE}/accounting/account/${accountId}/invoices/invoices`,88 {89 method: 'POST',90 headers: {91 Authorization: `Bearer ${accessToken}`,92 'Content-Type': 'application/json',93 'Api-Version': 'alpha',94 },95 body: JSON.stringify(payload),96 }97 );9899 if (!response.ok) {100 const errorData = await response.json();101 throw new Error(errorData.response?.errors?.[0]?.message || 'Invoice creation failed');102 }103104 const data = await response.json();105 const invoice = data.response.result.invoice;106107 return NextResponse.json({108 success: true,109 invoiceId: invoice.id,110 invoiceNumber: invoice.invoice_number,111 total: invoice.amount.amount,112 });113 } catch (error) {114 const message = error instanceof Error ? error.message : 'Unknown error';115 console.error('FreshBooks invoice creation failed:', message);116 return NextResponse.json(117 { error: 'Failed to create invoice', details: message },118 { status: 500 }119 );120 }121}122123export async function GET(request: NextRequest) {124 const accountId = process.env.FRESHBOOKS_ACCOUNT_ID;125 const { searchParams } = new URL(request.url);126 const clientId = searchParams.get('clientId');127128 if (!accountId || !process.env.FRESHBOOKS_CLIENT_ID) {129 return NextResponse.json({ error: 'FreshBooks is not configured' }, { status: 500 });130 }131132 try {133 const accessToken = await getAccessToken();134 const url = clientId135 ? `${FB_API_BASE}/accounting/account/${accountId}/invoices/invoices?search[customerid]=${clientId}`136 : `${FB_API_BASE}/accounting/account/${accountId}/invoices/invoices`;137138 const response = await fetch(url, {139 headers: {140 Authorization: `Bearer ${accessToken}`,141 'Api-Version': 'alpha',142 },143 });144145 if (!response.ok) throw new Error('Failed to fetch invoices');146147 const data = await response.json();148 return NextResponse.json(data.response.result);149 } catch (error) {150 const message = error instanceof Error ? error.message : 'Unknown error';151 return NextResponse.json({ error: message }, { status: 500 });152 }153}Pro tip: FreshBooks access tokens expire every 12 hours. By always calling getAccessToken() which uses the refresh token, your API routes automatically obtain fresh tokens without any manual intervention or redeployment.
Expected result: POSTing an invoice payload to /api/freshbooks/invoices creates a draft invoice in FreshBooks and returns the invoice ID and number. GET /api/freshbooks/invoices?clientId=... lists invoices for a specific client.
Configure Environment Variables and Deploy
Configure Environment Variables and Deploy
Add all FreshBooks credentials to Vercel. Open the Vercel Dashboard, navigate to your project, go to Settings → Environment Variables, and add the following variables for all three scopes (Production, Preview, Development). FRESHBOOKS_CLIENT_ID and FRESHBOOKS_CLIENT_SECRET come from your FreshBooks app settings at my.freshbooks.com dashboard → developer portal. FRESHBOOKS_REFRESH_TOKEN is the long-lived refresh token obtained after completing the initial OAuth authorization flow — this token is valid for 6 months and can be renewed programmatically. FRESHBOOKS_ACCOUNT_ID is your business account identifier visible in the FreshBooks dashboard URL (the numeric string after /account/). None of these variables should have the NEXT_PUBLIC_ prefix — all are server-side secrets. After saving variables in Vercel, trigger a redeployment from the Deployments tab. Test the deployed integration by creating a test invoice through your app and verifying it appears in FreshBooks. If you see token refresh errors after the initial setup, it means the refresh token has expired and you need to complete the OAuth flow again to generate a fresh one. For production apps where multiple users authenticate with their own FreshBooks accounts, you will need to implement a proper per-user OAuth flow with token storage in a database — this is a more complex architecture that the single-account pattern above does not cover.
Add a Recent Invoices section to the dashboard that fetches from GET /api/freshbooks/invoices on page load. Show invoices in a table with Invoice Number, Client Name, Total Amount, Due Date, and Status columns. Status should show as colored badges: green for Paid, yellow for Sent, red for Overdue (due date passed and not paid), and grey for Draft. Add a Create Invoice button that opens the invoice creation form in a modal. Include a total outstanding balance summary card at the top of the page.
Paste this in V0 chat
Pro tip: For local development, create a .env.local file with all four FreshBooks environment variables. Run npm run dev to test invoice creation locally before deploying — this lets you catch API issues without burning Vercel build minutes.
Expected result: The Vercel-deployed app creates FreshBooks invoices and fetches invoice lists successfully. Environment variables are configured for all deployment scopes and the integration works end-to-end in production.
Common use cases
Invoice Generator for Freelancers
A freelancer dashboard where you enter client name, line items, and amounts, and the app generates a FreshBooks invoice that is automatically sent to the client. V0 generates the invoice builder form; a Next.js API route creates the invoice via FreshBooks API and triggers email delivery. The dashboard shows a list of outstanding and paid invoices with their status.
Build a freelance invoice generator with a form that has fields for client name, project description, quantity, rate, and a tax rate percentage. Add line item rows dynamically with an Add Line Item button. Calculate subtotal, tax, and total automatically. Include a Send Invoice button that POSTs to /api/freshbooks/invoices with the form data. Show a success message with the invoice number when created. Use a clean professional design with a teal accent color.
Copy this prompt to try it in V0
Expense Tracking Dashboard
An expense logging interface where you quickly record business expenses with category, amount, date, and merchant name. The V0-generated form submits to a Next.js API route that creates an expense record in FreshBooks. A summary card shows total expenses by category for the current month.
Create an expense tracking page with a quick-add form containing category dropdown (Travel, Meals, Software, Equipment, Office, Other), amount input, merchant name, date picker, and notes textarea. Submit button POSTs to /api/freshbooks/expenses. Below the form, show a bar chart of expenses by category for the current month and a list of the last 10 expenses. Use a clean dashboard style.
Copy this prompt to try it in V0
Client Portal with Invoice History
A client-facing portal where clients can view their invoices, see which are outstanding versus paid, and download PDF copies. V0 generates the client dashboard with invoice status badges and totals; Next.js API routes fetch client-specific invoices from FreshBooks and serve them without exposing API credentials.
Design a client billing portal page. At the top, show the client name, total outstanding balance, and a 'Pay Now' button. Below, show a table of invoices with columns: Invoice Number, Date, Due Date, Amount, Status (Paid/Outstanding/Overdue with color badges). Data loads from GET /api/freshbooks/invoices?clientId=... on page load. Include a download icon per row. Use a professional white and blue design.
Copy this prompt to try it in V0
Troubleshooting
API route returns 'Failed to refresh FreshBooks access token'
Cause: The refresh token stored in FRESHBOOKS_REFRESH_TOKEN has expired (tokens expire after 6 months of inactivity), is incorrect, or the FRESHBOOKS_CLIENT_ID and FRESHBOOKS_CLIENT_SECRET do not match the app that generated the token.
Solution: Re-authorize your FreshBooks app by visiting the OAuth authorization URL again to generate a fresh refresh token. Ensure FRESHBOOKS_CLIENT_ID and FRESHBOOKS_CLIENT_SECRET match the app credentials exactly from the FreshBooks developer portal. Update FRESHBOOKS_REFRESH_TOKEN in Vercel with the new token and redeploy.
Invoice creation returns 422 or 'Invalid customerid'
Cause: The client ID passed to the API does not match a client record in your FreshBooks account, or the FRESHBOOKS_ACCOUNT_ID is incorrect.
Solution: Fetch your clients list first via GET /accounting/account/{accountId}/users/clients to get valid customerid values. Verify FRESHBOOKS_ACCOUNT_ID is the numeric ID from your FreshBooks dashboard URL, not your email or username. FreshBooks client IDs are integers, not strings.
FreshBooks API returns 403 Forbidden for all requests
Cause: The access token obtained via the refresh token does not have the required scopes, or the FreshBooks app was not granted the necessary permissions when the OAuth flow was originally completed.
Solution: Re-authorize the FreshBooks app and ensure you are requesting the correct OAuth scopes during authorization. FreshBooks scopes include admin:all:legacy for full access or more specific scopes like accounting:invoices:read and accounting:invoices:write for invoice operations. Check your app settings in the FreshBooks developer portal for the configured scopes.
Invoice line item amounts show as zero or are formatted incorrectly
Cause: The FreshBooks API expects amount values as strings with two decimal places, not JavaScript numbers. Passing a raw integer or float can cause validation failures or incorrect calculations.
Solution: Use .toFixed(2) when formatting monetary values for the FreshBooks API. The amount object must be a string like '150.00', not a number like 150. Both unit_cost and the top-level amount fields require this string format.
1// Correct amount formatting for FreshBooks2const amount = { amount: (quantity * rate).toFixed(2), code: 'USD' };Best practices
- Always use refresh tokens to obtain fresh access tokens in API routes rather than storing short-lived access tokens — this prevents 401 errors after the 12-hour expiry
- Store FRESHBOOKS_ACCOUNT_ID as an environment variable rather than hardcoding it so you can switch between test and production FreshBooks accounts without code changes
- Create invoices as drafts (status: 1) first to allow review before sending — use status: 2 to send immediately, but be careful about triggering emails to clients during testing
- Validate that client IDs exist before attempting invoice creation — fetching the client list and building a client selector dropdown prevents invalid customerid errors
- For multi-tenant apps where different users have different FreshBooks accounts, store OAuth tokens per user in a database rather than in environment variables
- Add currency handling explicitly — FreshBooks accounts can be in different currencies and the API requires the currency code to be specified in every amount object
- Use FreshBooks sandbox credentials (available in developer settings) for testing to avoid creating real invoices during development
Alternatives
Use QuickBooks instead of FreshBooks if your business needs full-featured double-entry bookkeeping, payroll integration, or your accountant requires QuickBooks — FreshBooks is simpler and better suited for freelancers and small agencies.
Choose Xero over FreshBooks if you need stronger multi-currency support, more advanced reporting, or integration with a wider ecosystem of third-party accounting apps.
Use Wave if you need free invoicing and accounting software — Wave's core features are free forever, while FreshBooks requires a paid subscription.
Frequently asked questions
Does FreshBooks require OAuth for all API access or can I use a simpler API key?
FreshBooks requires OAuth2 for all API access — there is no simple API key authentication option. However, for single-account integrations where you are accessing your own FreshBooks account, you can complete the OAuth flow once, store the refresh token in Vercel environment variables, and use it automatically from then on. This effectively behaves like an API key for your own account.
How long do FreshBooks access tokens and refresh tokens last?
FreshBooks access tokens expire after 12 hours and must be refreshed using the refresh token. Refresh tokens are long-lived but expire after 6 months of inactivity — using the refresh token to obtain new access tokens resets the 6-month timer. For production apps, implement automatic token refresh in your API routes so users never encounter token expiry errors.
Can I use the FreshBooks API to send invoices automatically or just create drafts?
The FreshBooks API supports both creating draft invoices (status: 1) and sending them immediately (status: 2). Setting status to 2 when creating the invoice triggers FreshBooks to send the invoice email to the client. You can also update an existing draft invoice's status to send it later using the PUT endpoint for the invoice resource.
Is there a FreshBooks sandbox environment for testing?
FreshBooks does not have a fully separate sandbox environment, but they provide test credentials in the developer portal that let you make API calls against a test account. Contact FreshBooks developer support at support.freshbooks.com to request sandbox access if your development workflow requires isolated test data.
How do I handle multiple currencies in FreshBooks invoices?
FreshBooks invoices are created in the currency of the client's account settings. When constructing amount objects in the API payload, specify the currency code explicitly as the code property (e.g., { amount: '150.00', code: 'EUR' }). Your FreshBooks account must have the target currency enabled in Settings → Currency.
Can V0 generate the full OAuth callback flow for FreshBooks?
Yes — you can prompt V0 to generate an OAuth callback route at app/api/freshbooks/callback/route.ts that exchanges the authorization code for tokens. Describe the flow: receive the code parameter from the URL, POST to FreshBooks token endpoint with your client credentials and the code, then store the returned tokens. For single-account apps, you typically only need to do this once to get your initial refresh token.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation