Skip to main content
RapidDev - Software Development Agency
lovable-integrationsEdge Function Integration

How to Integrate Lovable with FreshBooks

Integrating FreshBooks with Lovable uses Edge Functions to manage OAuth2 authentication and proxy FreshBooks API requests for clients, invoices, expenses, and time entries. Store your FreshBooks OAuth credentials in Cloud Secrets, create an Edge Function for the authorization code flow and token refresh, and build custom invoicing dashboards tailored to freelancers and service businesses. Setup takes 45-60 minutes.

What you'll learn

  • How to register a FreshBooks application and set up OAuth2 credentials
  • How to implement the FreshBooks OAuth2 authorization code flow in a Lovable app
  • How to store and refresh FreshBooks access tokens per user in Supabase
  • How to create and retrieve invoices, clients, and expenses via Edge Functions
  • How to build a custom freelancer invoicing dashboard with payment status tracking
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate14 min read50 minutesFinanceMarch 2026RapidDev Engineering Team
TL;DR

Integrating FreshBooks with Lovable uses Edge Functions to manage OAuth2 authentication and proxy FreshBooks API requests for clients, invoices, expenses, and time entries. Store your FreshBooks OAuth credentials in Cloud Secrets, create an Edge Function for the authorization code flow and token refresh, and build custom invoicing dashboards tailored to freelancers and service businesses. Setup takes 45-60 minutes.

Why integrate FreshBooks with Lovable?

FreshBooks serves over 30 million users who are primarily freelancers, consultants, and service-based small businesses. Its core strengths are the simplest invoice creation experience on the market, automatic payment reminders, time tracking tied to invoices, and project-based expense management. If your Lovable app serves freelancers or service businesses, connecting to FreshBooks gives you access to their existing client list, invoice history, and billing data without requiring them to double-enter everything.

Common use cases include building project management tools that create FreshBooks invoices when projects are marked complete, client portals that show outstanding invoices and payment history alongside project status, internal dashboards that track billable hours across clients, and automation tools that pull expense data from FreshBooks for tax preparation. The FreshBooks API makes all of these possible with clean REST endpoints and a well-documented OAuth2 flow.

FreshBooks uses the standard OAuth2 authorization code flow with PKCE, which means users consent to your app accessing their FreshBooks data through a familiar 'Authorize with FreshBooks' button flow. Unlike API key-based integrations that work with a single account, the OAuth2 pattern means you can build multi-tenant apps where each user connects their own FreshBooks account. This tutorial covers the full OAuth2 implementation including token storage, refresh token handling, and the main FreshBooks API operations you'll need for a billing dashboard.

Integration method

Edge Function Integration

FreshBooks has no native Lovable connector. Integration requires Supabase Edge Functions to handle FreshBooks' OAuth2 authorization code flow, store and refresh access tokens per user, and proxy all API requests for clients, invoices, expenses, and time entries. OAuth credentials are stored in Cloud Secrets. Because FreshBooks uses a user-facing OAuth2 flow, each user of your app must authorize access to their FreshBooks account through the standard consent screen.

Prerequisites

  • A Lovable project with Cloud enabled
  • A FreshBooks account with developer access — enable at my.freshbooks.com/develop
  • A FreshBooks application registered to get OAuth2 client_id and client_secret
  • A callback URL configured in your FreshBooks app pointing to your Lovable app's domain
  • Basic understanding of OAuth2 authorization code flow

Step-by-step guide

1

Register a FreshBooks application and configure OAuth2

Log in to your FreshBooks account and navigate to my.freshbooks.com/develop. Click 'Create an App'. Enter your application name, description, and set the Application Type to 'Web Application'. The Redirect URL (callback URL) is where FreshBooks will send the authorization code after a user consents to your app — set this to your Lovable app's deployed URL followed by '/auth/freshbooks/callback' (e.g., https://your-app.lovable.app/auth/freshbooks/callback). After creating the app, FreshBooks shows you the Client ID and Client Secret. Copy both immediately — the secret is displayed only once and you'll need to regenerate it if lost. The Client ID is a UUID-format string and the Client Secret is a longer random string. FreshBooks scopes control what your app can access. For a full invoicing dashboard, request: user:profile:read (user info), user:clients:read user:clients:write (client CRUD), user:invoices:read user:invoices:write (invoice CRUD), user:expenses:read user:expenses:write (expense CRUD), user:time_entries:read user:time_entries:write (time entry CRUD). Include only the scopes you actually need — fewer scopes improve user trust during the consent screen. The FreshBooks OAuth2 authorization endpoint is https://auth.freshbooks.com/service/auth/oauth/authorize. The token endpoint is https://api.freshbooks.com/auth/oauth/token. FreshBooks access tokens expire after 1 hour; refresh tokens expire after 7 days (but are extended with each use, making active integrations effectively permanent).

Pro tip: During development, you can use localhost:3000 as your redirect URL. When deploying to Lovable Cloud, you'll need to add the production domain as a second redirect URL in your FreshBooks app settings. FreshBooks allows multiple redirect URLs per app.

Expected result: Your FreshBooks app is created with Client ID and Client Secret saved. The redirect URL is configured to your app's callback path. OAuth2 scopes are set to cover the features you need.

2

Store FreshBooks credentials in Cloud Secrets

Open your Lovable project, click '+' in the top-right, select 'Cloud', and expand Secrets. Add the OAuth2 credentials as encrypted environment variables. Add FRESHBOOKS_CLIENT_ID with your application's Client ID. Add FRESHBOOKS_CLIENT_SECRET with the Client Secret. Add FRESHBOOKS_REDIRECT_URI with your exact callback URL (must match exactly what's registered in your FreshBooks app). Add FRESHBOOKS_AUTH_URL with 'https://auth.freshbooks.com/service/auth/oauth/authorize' and FRESHBOOKS_TOKEN_URL with 'https://api.freshbooks.com/auth/oauth/token'. For storing user tokens, create a Supabase table called freshbooks_tokens with columns: user_id (FK to auth users), account_id (FreshBooks account identifier), access_token, refresh_token, expires_at, created_at. Apply RLS so users can only see their own tokens. Use the service role key in your Edge Functions to read and update these tokens — the anon key respects RLS and would block the Edge Function from writing token updates.

Pro tip: The FRESHBOOKS_REDIRECT_URI must be identical (character-for-character) in both Cloud Secrets and your FreshBooks app registration. A trailing slash difference, HTTP vs HTTPS, or any whitespace will cause redirect_uri_mismatch errors that can be confusing to debug.

Expected result: All FreshBooks OAuth2 credentials appear in Cloud Secrets. The freshbooks_tokens Supabase table is created with RLS enabled.

3

Implement the OAuth2 authorization flow

FreshBooks OAuth2 works in three steps: the user clicks 'Connect FreshBooks' in your app, they are redirected to FreshBooks' authorization page where they log in and consent, and FreshBooks redirects back to your app with an authorization code. Your Edge Function then exchanges this code for access and refresh tokens. Step 1 (Initiate): Build a 'Connect FreshBooks' button that constructs the FreshBooks authorization URL with your client_id, redirect_uri, scope, and a state parameter (a random string to prevent CSRF attacks). The user navigates to this URL. Step 2 (Callback): Create a route in your Lovable app at the callback URL. When FreshBooks redirects with ?code=XXX&state=YYY, your app's callback page sends the code to an Edge Function. Step 3 (Token Exchange): The Edge Function POSTs to the FreshBooks token endpoint with grant_type=authorization_code, the code, client_id, client_secret, and redirect_uri. FreshBooks returns access_token, refresh_token, token_type, and expires_in (seconds). Store these in the freshbooks_tokens table. For token refresh: when making FreshBooks API calls, check if the access token expires within 5 minutes. If so, call the token endpoint with grant_type=refresh_token and your refresh_token to get a new access token. Update the stored tokens. FreshBooks refresh tokens are rotated on each use, so always update both the access and refresh token after a refresh.

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/freshbooks-oauth/index.ts. Support: action='exchange' with code and user_id — POST to FRESHBOOKS_TOKEN_URL with grant_type=authorization_code, code, client_id, client_secret, redirect_uri; store access_token, refresh_token, expires_at in freshbooks_tokens using service role key; fetch user's FreshBooks identity from /auth/api/v1/users/me and store account_id; return {success: true}. action='get-token' with user_id — check freshbooks_tokens for unexpired token (refresh if expiring soon); return {access_token, account_id}.

Paste this in Lovable chat

supabase/functions/freshbooks-oauth/index.ts
1// supabase/functions/freshbooks-oauth/index.ts
2import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
3import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
4
5const CLIENT_ID = Deno.env.get("FRESHBOOKS_CLIENT_ID") ?? "";
6const CLIENT_SECRET = Deno.env.get("FRESHBOOKS_CLIENT_SECRET") ?? "";
7const REDIRECT_URI = Deno.env.get("FRESHBOOKS_REDIRECT_URI") ?? "";
8const TOKEN_URL = Deno.env.get("FRESHBOOKS_TOKEN_URL") ?? "https://api.freshbooks.com/auth/oauth/token";
9const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization" };
10
11serve(async (req) => {
12 if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
13 const supabase = createClient(Deno.env.get("SUPABASE_URL") ?? "", Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "");
14 const body = await req.json();
15
16 try {
17 if (body.action === "exchange") {
18 const { code, user_id } = body;
19 const res = await fetch(TOKEN_URL, {
20 method: "POST",
21 headers: { "Content-Type": "application/x-www-form-urlencoded" },
22 body: new URLSearchParams({ grant_type: "authorization_code", code, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, redirect_uri: REDIRECT_URI }),
23 });
24 const data = await res.json();
25 if (!data.access_token) throw new Error(data.error_description ?? "Token exchange failed");
26
27 const meRes = await fetch("https://api.freshbooks.com/auth/api/v1/users/me", { headers: { Authorization: `Bearer ${data.access_token}` } });
28 const me = await meRes.json();
29 const accountId = me.response?.roles?.[0]?.accountid ?? "";
30
31 const expiresAt = new Date(Date.now() + data.expires_in * 1000).toISOString();
32 await supabase.from("freshbooks_tokens").upsert({ user_id, account_id: accountId, access_token: data.access_token, refresh_token: data.refresh_token, expires_at: expiresAt }, { onConflict: "user_id" });
33 return new Response(JSON.stringify({ success: true, account_id: accountId }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
34 }
35
36 if (body.action === "get-token") {
37 const { user_id } = body;
38 const { data: token } = await supabase.from("freshbooks_tokens").select("*").eq("user_id", user_id).single();
39 if (!token) throw new Error("No FreshBooks connection found. Please connect your account.");
40
41 if (new Date(token.expires_at) < new Date(Date.now() + 300_000)) {
42 const res = await fetch(TOKEN_URL, {
43 method: "POST",
44 headers: { "Content-Type": "application/x-www-form-urlencoded" },
45 body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: token.refresh_token, client_id: CLIENT_ID, client_secret: CLIENT_SECRET }),
46 });
47 const data = await res.json();
48 const expiresAt = new Date(Date.now() + data.expires_in * 1000).toISOString();
49 await supabase.from("freshbooks_tokens").update({ access_token: data.access_token, refresh_token: data.refresh_token, expires_at: expiresAt }).eq("user_id", user_id);
50 return new Response(JSON.stringify({ access_token: data.access_token, account_id: token.account_id }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
51 }
52
53 return new Response(JSON.stringify({ access_token: token.access_token, account_id: token.account_id }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
54 }
55
56 return new Response(JSON.stringify({ error: "Unknown action" }), { status: 400, headers: corsHeaders });
57 } catch (err) {
58 return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } });
59 }
60});

Pro tip: FreshBooks account IDs are found in the /users/me response under response.roles[0].accountid. This accountid is required for all subsequent API calls as part of the URL path: /accounting/account/{accountid}/invoices/invoices. Store it alongside the token in freshbooks_tokens.

Expected result: The freshbooks-oauth Edge Function handles token exchange and refresh. Clicking 'Connect FreshBooks', completing the OAuth flow, and returning to your app stores a valid token in Supabase and returns account_id.

4

Build the invoicing and client management Edge Function

With tokens managed, create the FreshBooks data Edge Function. FreshBooks API endpoints follow the pattern /accounting/account/{accountid}/invoices/invoices for invoices, /accounting/account/{accountid}/contacts/clients for clients, and /accounting/account/{accountid}/expenses/expenses for expenses. All requests need the user's access_token in the Authorization Bearer header. Get the token and account_id by calling freshbooks-oauth with action='get-token' and the current user_id at the start of each data request. For creating an invoice, POST to the invoices endpoint with a body containing: customerid (FreshBooks client ID), create_date (YYYY-MM-DD), due_offset_days (days until due from create date), currency_code, and lines (array of line items with name, description, unit_cost, quantity). FreshBooks returns the created invoice with its invoiceid that you can use to track it. For listing invoices, GET from the invoices endpoint with optional query parameters: payment_status (unpaid, paid, overdue), date_min/date_max for date range filtering, and page/per_page for pagination. The response includes invoice totals, due dates, and outstanding balances.

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/freshbooks-api/index.ts. It should call freshbooks-oauth to get the current user's access_token and account_id. Support: action='list-clients' — GET /accounting/account/{accountid}/contacts/clients; return array of {id, name, email, organization}. action='list-invoices' with optional status param — GET invoices filtered by payment_status; return array of {id, invoice_number, client_name, amount, due_date, status, outstanding}. action='create-invoice' with client_id, due_days, lines[] — POST to create invoice; return {invoice_id, invoice_number}.

Paste this in Lovable chat

Pro tip: FreshBooks uses different ID field names in different contexts: clientid is used in invoices to reference a client, but the client list returns id. Keep track of which ID field to use where — sending the wrong ID field name results in a 400 error without a clear error message.

Expected result: The freshbooks-api Edge Function lists clients and invoices from the connected FreshBooks account. Creating an invoice via the Edge Function creates it in FreshBooks and returns the invoice number.

Common use cases

Project-to-invoice automation

A project management app automatically creates FreshBooks invoices when projects reach billing milestones. When a project is marked complete or a milestone is reached, an Edge Function calls the FreshBooks API to create a new invoice for the associated client with line items from the project's time entries. The invoice is automatically sent to the client or saved as a draft for review.

Lovable Prompt

Add a 'Create Invoice' button to each project detail page. When clicked, call an Edge Function that reads the project's client name and unbilled time entries from Supabase, then calls the FreshBooks API to create an invoice with the project name as the invoice title and each time entry as a line item with description, hours, and rate. After creation, show the invoice number and provide a link to view it in FreshBooks. Store the FreshBooks invoice_id in the Supabase project record.

Copy this prompt to try it in Lovable

Outstanding invoices and AR dashboard

A billing dashboard shows a freelancer all their outstanding invoices with aging information — which clients owe money, how long each invoice has been outstanding, and total AR by age bucket. An Edge Function fetches all invoices from FreshBooks with 'unpaid' or 'overdue' status and returns them sorted by due date. The frontend displays AR aging buckets (current, 30 days, 60 days, 90+ days) with total amounts.

Lovable Prompt

Build an accounts receivable dashboard. Fetch all FreshBooks invoices with outstanding balance (status not 'paid') via an Edge Function. Group them by age: current (due in future), 1-30 days overdue, 31-60 days overdue, 60+ days overdue. Show a summary of total outstanding by bucket, and a detailed list of each overdue invoice with client name, invoice number, amount, due date, and days overdue. Add a 'Send Reminder' button per invoice that updates the invoice in FreshBooks to send an automatic payment reminder email.

Copy this prompt to try it in Lovable

Time tracking and billable hours report

A combined time tracker and billing tool lets freelancers log time against clients and projects, with automatic calculation of billable amounts at their hourly rate. Time entries sync bidirectionally with FreshBooks. A monthly report shows total hours and revenue by client, helping freelancers understand which clients are most profitable.

Lovable Prompt

Build a time tracking page where users can log hours against clients and projects selected from their FreshBooks client list. Store time entries in Supabase with client_id, project_id, hours, description, date, and hourly_rate. Add a 'Sync to FreshBooks' button that calls an Edge Function to create time entries in FreshBooks for all unsynced Supabase records. Build a monthly summary chart showing hours and dollar value per client, using data from the Supabase time_entries table.

Copy this prompt to try it in Lovable

Troubleshooting

OAuth redirect fails with 'redirect_uri_mismatch' error

Cause: The redirect_uri passed in the authorization URL or token exchange request doesn't exactly match the redirect URL registered in your FreshBooks application settings. Even a trailing slash difference causes this error.

Solution: Open my.freshbooks.com/develop, open your app settings, and verify the exact Redirect URL stored there. Copy it exactly into your FRESHBOOKS_REDIRECT_URI Cloud Secret. Make sure the URL you use to initiate OAuth matches this exactly — same protocol (https), same domain, same path, same capitalization. During development, ensure your local dev URL is also registered as an allowed redirect URL.

FreshBooks API returns 401 after access token appears valid

Cause: FreshBooks access tokens expire after 1 hour. If your app cached the token and uses it past expiry, all API calls return 401. The token refresh logic may not be triggering correctly.

Solution: In the freshbooks-oauth Edge Function's get-token action, check for expiry with a 5-minute buffer: if token expires within the next 300 seconds, refresh it before returning. Verify the expires_at value is stored correctly as a timestamp (not as an epoch integer). Log the token expiry time in Cloud Logs to confirm the refresh is triggering at the right time.

typescript
1const expiresAt = new Date(Date.now() + data.expires_in * 1000).toISOString();
2// Store as ISO string, compare with new Date(token.expires_at) < new Date(Date.now() + 300_000)

Invoice creation returns 422 Unprocessable Entity

Cause: FreshBooks invoice creation requires specific field formats and valid reference IDs. Common causes: the customerid doesn't match a real client in the account, create_date is not in YYYY-MM-DD format, line item amounts are strings instead of numbers, or required fields like currency_code are missing.

Solution: Check the Cloud Logs for the full 422 response body — FreshBooks provides detailed validation errors identifying which fields are invalid. Verify the customerid is a valid FreshBooks client ID (not your Supabase ID). Ensure all monetary amounts are numeric values (not strings), dates are in YYYY-MM-DD format, and the currency_code is a valid ISO 4217 code like 'USD' or 'EUR'.

Best practices

  • Always refresh FreshBooks access tokens proactively when they're within 5 minutes of expiry rather than waiting for a 401 error to trigger a retry — this prevents users from seeing failed API calls.
  • Store the FreshBooks account_id alongside the access token in your database — all FreshBooks API endpoints require the accountid in the URL path, and looking it up separately on every request adds unnecessary latency.
  • Use FreshBooks' invoice numbering system rather than generating your own — FreshBooks auto-increments invoice numbers and ensures uniqueness within an account, which matters for financial record integrity.
  • Handle FreshBooks' rate limits gracefully — the API allows 6,000 requests per hour per user. For bulk operations (syncing historical data), implement request queuing with delays between batches.
  • Test the token refresh flow explicitly by temporarily setting the stored expires_at to a past time and verifying your Edge Function correctly fetches a new token before making the data request.
  • Implement a 'Disconnect FreshBooks' option in your app that deletes the freshbooks_tokens row and revokes the token via FreshBooks' token revocation endpoint, giving users control over their data.
  • For freelancers using FreshBooks Classic vs. FreshBooks (New), note that the APIs differ significantly — this tutorial covers the current FreshBooks API, not the legacy FreshBooks Classic API.

Alternatives

Frequently asked questions

Can I build a FreshBooks integration without each user having their own FreshBooks account?

If you're building an internal tool for a single business, you can use your own FreshBooks OAuth credentials and store a single access token rather than implementing per-user OAuth. In this case, use your own FreshBooks account and store the refresh token in Cloud Secrets rather than in a per-user table. For multi-tenant apps where each user connects their own FreshBooks account, the per-user OAuth flow described in this tutorial is required.

What's the difference between FreshBooks and QuickBooks for a Lovable integration?

FreshBooks is designed for freelancers and service businesses with the simplest possible invoicing workflow — it has a cleaner API for invoice creation, time tracking, and client management. QuickBooks is designed for businesses needing full accounting with chart of accounts, bank reconciliation, payroll, and complex reporting. If your users primarily need to send invoices and track client payments, FreshBooks is easier to integrate. If they need full bookkeeping, choose QuickBooks.

Can I send invoices directly from my Lovable app through FreshBooks?

Yes — FreshBooks' API supports sending invoices via email directly when creating them by including the send_email: true parameter in the invoice creation request. You can also use FreshBooks' email templates and the invoice will appear in the client's FreshBooks client portal for online payment. Alternatively, save invoices as drafts via the API and let users review them in FreshBooks before sending.

How do I handle FreshBooks' late payment reminders in my app?

FreshBooks has built-in automatic late payment reminders configured in their dashboard rather than via API. For custom reminder workflows, use the invoices API to query overdue invoices (status=overdue) on a schedule, then use your own email sending (via Resend Edge Function) or Lovable's built-in email capabilities to send custom reminder emails. This gives you more control over reminder timing and message content than FreshBooks' built-in feature.

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.