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

How to Integrate Lovable with Expensify

Integrating Expensify with Lovable uses Edge Functions to call Expensify's Integration Server API with partner credentials. Store your partnerUserID and partnerUserSecret in Cloud Secrets, create Edge Functions to create expense reports, upload receipts, and fetch report data, and build employee expense submission workflows with approval tracking. Setup takes 40 minutes.

What you'll learn

  • How to obtain Expensify Integration Server API credentials (partner credentials)
  • How to structure requests to Expensify's unique single-endpoint API format
  • How to create expense reports and individual expenses via Edge Functions
  • How to upload receipt images and trigger SmartScan OCR processing
  • How to build an expense submission workflow with status tracking in Supabase
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate13 min read40 minutesFinanceMarch 2026RapidDev Engineering Team
TL;DR

Integrating Expensify with Lovable uses Edge Functions to call Expensify's Integration Server API with partner credentials. Store your partnerUserID and partnerUserSecret in Cloud Secrets, create Edge Functions to create expense reports, upload receipts, and fetch report data, and build employee expense submission workflows with approval tracking. Setup takes 40 minutes.

Why integrate Expensify with Lovable?

Expensify is one of the most widely deployed expense management tools in the corporate world, used by everything from startups to Fortune 500 companies. Its core value is making expense reports fast — employees photograph receipts, Expensify's SmartScan OCR extracts the data, and the expense is auto-submitted to the company's policy for approval. When connected to accounting software like QuickBooks or NetSuite, approved expenses flow directly into the company's books without manual entry.

For Lovable developers, integrating Expensify into an internal tool means employees never need to leave your app to submit expenses. You can build expense capture directly into project management tools (log a project expense without switching to Expensify), field service apps (technicians submit parts receipts on-site), or client portals (contractors submit reimbursable expenses as part of their project workflow). The Expensify API handles the actual expense report management and approval routing — you just need to create the expenses and let Expensify do the rest.

Expensify's API is architecturally different from modern REST APIs — it uses a single endpoint that accepts JSON-encoded commands in a form parameter. This unusual design works perfectly in Edge Functions but requires understanding the request structure before writing code. The key concepts are: reports (expense report containers), transactions (individual expenses within a report), and policies (approval rules that determine how reports are processed). This tutorial covers the most common workflow: creating a report, adding expenses with receipts, and submitting for approval.

Integration method

Edge Function Integration

Expensify has no native Lovable connector. Integration requires Supabase Edge Functions to call Expensify's Integration Server API using partner credentials (partnerUserID and partnerUserSecret) stored in Cloud Secrets. The API uses a unique request format — a POST to a single endpoint with a JSON 'requestJobDescription' parameter encoding the command and input data. Edge Functions handle all Expensify API calls to keep credentials server-side.

Prerequisites

  • A Lovable project with Cloud enabled
  • An Expensify account — sign up at expensify.com
  • Expensify Integration Server credentials (partnerUserID and partnerUserSecret) — request at use.expensify.com/tools/integrations
  • The email address of the Expensify account that will own the created reports
  • Basic understanding of Expensify's report and transaction concepts

Step-by-step guide

1

Obtain Expensify Integration Server credentials

Expensify's API uses a dedicated integration authentication system separate from your regular Expensify login. Go to use.expensify.com/tools/integrations while logged into your Expensify account. This page generates your Integration Server credentials: a partnerUserID (a long string starting with 'partner-') and a partnerUserSecret (a long random string). These credentials authenticate your application's API requests. Unlike OAuth2 where each user authorizes separately, Expensify's integration credentials are account-level — they allow you to act on behalf of any user within your Expensify policy when you include their email in API requests. This is appropriate for internal tool integrations where the app acts on behalf of employees. The Expensify API endpoint is a single URL: https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations. All commands are sent as POST requests to this URL with a form-encoded body containing a 'requestJobDescription' JSON parameter. The JSON encodes the command type (create, export, update), input data, and any credentials needed for the specific operation. Expensify provides API documentation at integrations.expensify.com. Key commands are: createTransaction (add an expense to a report), createReport (create a new expense report), submitReport (submit a report for approval), and exportReports (retrieve report data). Test your credentials by running a simple exportReports command to retrieve a list of existing reports.

Pro tip: The Integration Server credentials are different from your Expensify policy admin credentials. Store them separately in Cloud Secrets. The partnerUserID looks like 'partner-a1b2c3d4' and the partnerUserSecret is a long alphanumeric string — make sure you copy the complete values without truncation.

Expected result: You have your Expensify partnerUserID and partnerUserSecret copied from the Expensify integrations page. Keep these secure — they provide API access to your Expensify account.

2

Store Expensify credentials in Cloud Secrets

Open your Lovable project, click '+', select 'Cloud', and expand Secrets. Add EXPENSIFY_PARTNER_USER_ID with your partnerUserID value and EXPENSIFY_PARTNER_USER_SECRET with your partnerUserSecret value. Also add EXPENSIFY_API_URL with 'https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations' so your Edge Function can reference the endpoint without hardcoding it. For reports that belong to specific employees, you'll need their Expensify email addresses. You can store a default EXPENSIFY_POLICY_ID if your organization has a specific Expensify policy ID to assign reports to. Policy IDs are found in your Expensify workspace settings under Workspace → Settings → General → ID. Expensify's partner credentials provide significant access to your organization's expense data — they can view all reports across policies associated with your account. Protect them with the same care as admin credentials. Never log the credentials in Cloud Logs or include them in error messages returned to the frontend.

Pro tip: If your organization uses multiple Expensify policies (common in multi-department companies), store the specific policy IDs you need in Cloud Secrets as well. The policy ID controls which approval workflows and spending categories apply to reports created via the API.

Expected result: EXPENSIFY_PARTNER_USER_ID, EXPENSIFY_PARTNER_USER_SECRET, and EXPENSIFY_API_URL appear in Cloud Secrets.

3

Create the Expensify API Edge Function

The Expensify API uses a unique request format. All commands are POST requests to the single integration endpoint with the body formatted as application/x-www-form-urlencoded containing a 'requestJobDescription' key whose value is a JSON string. This JSON object contains: type (Expense or File), dry-run (boolean for testing without side effects), credentials (partnerUserID and partnerUserSecret), inputSettings (command-specific parameters), and outputSettings. For creating a transaction (expense), the command structure is: type 'Expense' or 'transaction', inputSettings with reportOwnerEmail (the employee's email), reportName, and an array of transactions each containing merchant, created (date), amount (in cents as integer), currency, category, and optional transactionID and receipt. For uploading a receipt via SmartScan, use the 'file' command type with the receipt as base64 content. Expensify processes the image and returns extracted data. For submitting a report, use the updateReportStatus command with the reportID and the 'SUBMIT' action. Build the Edge Function to accept a simplified action interface (create-expense, submit-report, get-reports) and internally construct the correct requestJobDescription JSON for each action.

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/expensify-api/index.ts. Read EXPENSIFY_PARTNER_USER_ID, EXPENSIFY_PARTNER_USER_SECRET, EXPENSIFY_API_URL from Deno.env. Build a helper that POSTs to the Expensify API with the requestJobDescription format. Support: action='create-expense' with employee_email, merchant, amount_cents, date, category, optional receipt_base64 — create a transaction on the employee's current open report; action='submit-report' with report_id and employee_email — submit the report for approval; action='get-reports' with employee_email — export recent reports with status. Return structured results for each.

Paste this in Lovable chat

supabase/functions/expensify-api/index.ts
1// supabase/functions/expensify-api/index.ts
2import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
3
4const PARTNER_ID = Deno.env.get("EXPENSIFY_PARTNER_USER_ID") ?? "";
5const PARTNER_SECRET = Deno.env.get("EXPENSIFY_PARTNER_USER_SECRET") ?? "";
6const API_URL = Deno.env.get("EXPENSIFY_API_URL") ?? "https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations";
7
8const corsHeaders = {
9 "Access-Control-Allow-Origin": "*",
10 "Access-Control-Allow-Methods": "POST, OPTIONS",
11 "Access-Control-Allow-Headers": "Content-Type, Authorization",
12};
13
14async function callExpensify(jobDescription: Record<string, unknown>) {
15 const body = new URLSearchParams({
16 requestJobDescription: JSON.stringify({
17 credentials: { partnerUserID: PARTNER_ID, partnerUserSecret: PARTNER_SECRET },
18 ...jobDescription,
19 }),
20 });
21 const res = await fetch(API_URL, { method: "POST", body });
22 const text = await res.text();
23 try { return JSON.parse(text); } catch { return { raw: text }; }
24}
25
26serve(async (req) => {
27 if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
28 try {
29 const body = await req.json();
30 const { action } = body;
31
32 if (action === "create-expense") {
33 const { employee_email, merchant, amount_cents, date, category } = body;
34 const data = await callExpensify({
35 type: "Expense",
36 inputSettings: {
37 type: "expenses",
38 employeeEmail: employee_email,
39 transactions: [{ merchant, created: date, amount: amount_cents, currency: "USD", category: category ?? "" }],
40 },
41 });
42 return new Response(JSON.stringify(data), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
43 }
44
45 if (action === "get-reports") {
46 const { employee_email } = body;
47 const data = await callExpensify({
48 type: "file",
49 onReceive: { immediateResponse: ["returnRandomFileName"] },
50 inputSettings: {
51 type: "combinedReportData",
52 reportState: "SUBMITTED,APPROVED,REIMBURSED",
53 filters: { employeeEmail: employee_email },
54 },
55 outputSettings: { fileExtension: "json" },
56 });
57 return new Response(JSON.stringify(data), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
58 }
59
60 return new Response(JSON.stringify({ error: "Unknown action" }), { status: 400, headers: corsHeaders });
61 } catch (err) {
62 return new Response(JSON.stringify({ error: err.message }), {
63 status: 500,
64 headers: { ...corsHeaders, "Content-Type": "application/json" },
65 });
66 }
67});

Pro tip: Expensify's amount field uses cents as integers, not decimal dollars. $25.50 should be sent as 2550. Always multiply dollar amounts by 100 and round to the nearest integer before sending to the API.

Expected result: The expensify-api Edge Function is deployed. Calling it with action='create-expense' creates a transaction in the specified employee's Expensify account.

4

Build the expense submission UI

Build the employee expense submission workflow. The UI should be simple and fast — employees should be able to submit an expense in under 30 seconds including receipt upload. Create an expense submission form with: a receipt photo upload field (camera capture for mobile), merchant name, date (default today), amount in dollars, category dropdown (populated from your organization's Expensify categories or a standard list), and optional notes. For the category list, query your Expensify policy's categories via the API or maintain a static list synchronized occasionally. On form submission, call the expensify-api Edge Function with action='create-expense'. Convert the dollar amount to cents before sending. If a receipt was uploaded, encode it as base64 and include it in the API call for SmartScan processing. Display a list of the user's submitted expenses with their current status (open, submitted, approved, reimbursed) by storing expense records in Supabase after creation. Sync statuses from Expensify periodically using get-reports to keep the status display current. For the approval workflow, Expensify handles approval routing based on your company's configured policies. Your app just needs to show employees whether their reports have been approved — the approver actions happen within Expensify itself.

Lovable Prompt

Build an expense submission page. A form with receipt photo upload, merchant name, date (default today), amount (dollar input), and category dropdown (Meals, Travel, Supplies, Other). On Submit: convert amount to cents, call expensify-api Edge Function with action='create-expense' using the current user's email, save the expense to a Supabase user_expenses table (merchant, amount, date, category, expensify_response, status='submitted', created_at). Show a submitted expenses list below the form with status badges. Add a Refresh Status button that calls action='get-reports' and updates statuses in Supabase.

Paste this in Lovable chat

Pro tip: For mobile users, add accept='image/*' capture='camera' to your receipt upload input to open the device camera directly. This dramatically speeds up the receipt capture workflow since employees can photograph receipts in context (at a restaurant, retail store, etc.) without a separate app.

Expected result: Employees can submit expenses via the form. Submitted expenses appear in the list with 'submitted' status. The Refresh button updates statuses from Expensify.

Common use cases

In-app receipt capture and expense submission

An internal tool lets employees photograph receipts and submit expenses without opening a separate Expensify app. The receipt image is uploaded to Expensify's receipt endpoint, SmartScan processes it, and the extracted data is added as an expense transaction on an open report for the employee. A status indicator shows when the report is submitted and approved.

Lovable Prompt

Build an expense submission page. Add a photo upload input. When a receipt is uploaded: 1) call an Edge Function to create an Expensify expense transaction using the receipt image with the employee's email as the reportOwnerEmail; 2) use Expensify SmartScan to auto-extract merchant, date, and amount; 3) save the transaction details to Supabase; 4) show 'Processing...' while SmartScan runs and then display the extracted data. Add a Submit Report button that calls the Edge Function to submit the current open report for approval.

Copy this prompt to try it in Lovable

Project expense tracking with Expensify sync

A project management app lets project managers add project-related expenses directly within the project view. Each expense is synced to Expensify under a project-specific expense report. At project close, the manager submits the project expense report for approval and reimbursement through the existing Expensify workflow.

Lovable Prompt

Add expense tracking to each project page. A 'Log Expense' button opens a form with fields: date, merchant, amount, category, and optional receipt photo. Saving the expense calls an Edge Function to create an Expensify transaction on a report named after the project, add it to an expenses table in Supabase with expensify_transaction_id, and display it in the project's expense tab. A 'Submit to Expensify' button on the project page calls an Edge Function to submit all pending expenses as an Expensify report for approval.

Copy this prompt to try it in Lovable

Expense report status dashboard

An HR or finance dashboard shows the status of all expense reports submitted by team members — which are pending approval, which have been approved and are awaiting reimbursement, and which have been reimbursed. The Edge Function calls Expensify's report retrieval command to fetch report statuses and syncs them to Supabase. Managers can see their direct reports' pending approvals in one view.

Lovable Prompt

Build an expense reports dashboard. Create an Edge Function that calls Expensify's exportReports command to fetch all expense reports for a list of employee emails stored in Supabase. Return each report's ID, title, employee name, total amount, submission date, and approval status (open, submitted, approved, reimbursed). Display as a table grouped by status, with total pending reimbursement amount displayed prominently. Update statuses on a daily sync schedule.

Copy this prompt to try it in Lovable

Troubleshooting

API returns 'Error: Invalid partnerUserID or partnerUserSecret'

Cause: The partnerUserID or partnerUserSecret in Cloud Secrets is incorrect, truncated, or copied with extra whitespace. Expensify's credentials are long strings that can be accidentally truncated when copying.

Solution: Go back to use.expensify.com/tools/integrations, regenerate or recopy both credentials, and update Cloud Secrets with the complete values. Use your browser's developer tools to copy the full text of each field rather than the visible truncated display. Verify by checking that the partnerUserID starts with 'partner-' and is approximately 25-30 characters long.

Expense is created but receipt is not attached or SmartScan doesn't run

Cause: Receipt uploads require a specific format in the requestJobDescription — the receipt must be base64 encoded and included in the transaction object with the correct field name. If the field name or encoding is wrong, Expensify creates the transaction without the receipt.

Solution: Check Expensify's API documentation for the exact receipt field name in transaction objects (typically 'receipt' with a 'fileExtension' sub-field). Ensure the base64 string doesn't include the data URL prefix — strip 'data:image/jpeg;base64,' before sending. SmartScan processing is asynchronous; the expense appears immediately but the OCR data populates over the next 30-60 seconds.

typescript
1// Strip data URL prefix before encoding
2const base64 = receiptDataUrl.replace(/^data:image\/\w+;base64,/, '');

get-reports action returns a file URL instead of JSON data

Cause: Expensify's export commands return a URL to a generated file rather than inline data. The API generates the file asynchronously and returns a reference that must be fetched separately.

Solution: For the exportReports command, use the onReceive.immediateResponse option with ['returnRandomFileName'] to get synchronous JSON results when possible. For large exports, Expensify may still return a file URL — add a step in your Edge Function to fetch that URL and parse the JSON content.

Best practices

  • Always convert dollar amounts to cents (integer) before sending to Expensify — the API expects amounts as whole-number cents, not decimal dollar values.
  • Store submitted expenses in Supabase immediately after API call success rather than syncing back from Expensify — this gives users instant confirmation even before Expensify processes the expense.
  • Implement an employee email lookup to map your app's user accounts to their Expensify emails — if users register with different emails in your app and Expensify, expenses will be created under the wrong account.
  • Use Expensify's dry-run mode during development to test API calls without creating actual expenses — add 'dry-run': true to your requestJobDescription and check that the API accepts your request structure before going live.
  • Sync expense statuses from Expensify on a schedule (daily) rather than on every page load — Expensify's API is not optimized for real-time polling and status changes happen slowly.
  • Provide a direct link to the Expensify report in your UI once created — employees can navigate to Expensify directly for complex editing tasks while your app handles quick captures.
  • Inform users clearly that submitted expenses require approval in Expensify according to your company's policy — your app submits the expense, but approval routing and reimbursement happen in Expensify.

Alternatives

Frequently asked questions

Do my employees need Expensify accounts to submit expenses through my Lovable app?

Yes — Expensify creates expense reports under specific email addresses, so each employee who submits expenses needs a corresponding Expensify account (free accounts are available). When creating expenses via the API, you specify the employeeEmail, and Expensify creates or adds to that user's reports. Employees can then log into Expensify to see their reports, add details, and track approval status — or they can use your Lovable app for all of that if you implement the status syncing features.

What's the difference between Expensify and Dext for a Lovable integration?

Expensify is employee-facing expense management — employees submit expenses, managers approve, finance reimburses. It's focused on the expense claim workflow. Dext (formerly Receipt Bank) is accountant-facing document processing — it takes receipts and invoices from clients and converts them into bookkeeping entries. If your users are employees claiming reimbursement, use Expensify. If your users are accountants processing client documents, use Dext.

Can I trigger expense approvals from my Lovable app?

You can submit reports for approval via the API, which initiates the approval workflow configured in your Expensify policy. However, the actual approval action (clicking Approve or Reject) happens in Expensify's web or mobile interface — there is no API endpoint for managers to approve individual expenses through a third-party app. Your app can show approval status but managers need to use Expensify directly to approve.

How do I handle multi-currency expenses in Expensify?

Each transaction in Expensify requires a currency field with an ISO 4217 currency code (USD, EUR, GBP, etc.). The amount should be in the specified currency's base unit (cents for USD, euro cents for EUR). Expensify handles currency conversion to the policy's reimbursement currency automatically. Store the original currency and amount in your Supabase table alongside the converted amount for audit purposes.

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.