Skip to main content
RapidDev - Software Development Agency
replit-integrationsStandard API Integration

How to Integrate Replit with Expensify

To integrate Replit with Expensify, obtain a partnerUserID and partnerUserSecret from Expensify's Integration Server, store them in Replit Secrets (lock icon πŸ”’), and use your Python or Node.js backend to call the Expensify Integration Server API to create, retrieve, and export expense reports. Deploy on Autoscale for on-demand expense processing.

What you'll learn

  • How to obtain Expensify Integration Server credentials (partnerUserID and partnerUserSecret)
  • How to store Expensify credentials securely in Replit Secrets
  • How to create expense reports and add expenses using the Integration Server API from Python and Node.js
  • How to retrieve and export expense report data from Expensify
  • How to handle asynchronous Expensify report exports that use a job-based response pattern
Book a free consultation
4.9Clutch rating ⭐
600+Happy partners
17+Countries served
190+Team members
Intermediate13 min read35 minutesPaymentMarch 2026RapidDev Engineering Team
TL;DR

To integrate Replit with Expensify, obtain a partnerUserID and partnerUserSecret from Expensify's Integration Server, store them in Replit Secrets (lock icon πŸ”’), and use your Python or Node.js backend to call the Expensify Integration Server API to create, retrieve, and export expense reports. Deploy on Autoscale for on-demand expense processing.

Why Connect Replit to Expensify?

Expensify is widely used by small and mid-sized businesses to manage employee expense reports, receipt scanning, and reimbursement approvals. The Expensify Integration Server API extends this platform to custom workflows: you can create expenses programmatically from purchase data, automatically submit reports for approval, and export report data to your own accounting or analytics systems without manual exports from the Expensify web interface.

The Expensify Integration Server API has a distinctive design compared to most REST APIs. All requests go to a single endpoint (https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations) as HTTP POST requests with a JSON payload in a form field called 'requestJobDescription'. The JSON payload includes both your credentials and the operation you want to perform. Some operations (like exporting large report lists) are asynchronous β€” Expensify queues the job and returns a jobID you poll for results.

Replit's Secrets system (lock icon πŸ”’ in the sidebar) is the right place to store your partnerUserID and partnerUserSecret. These credentials grant API-level access to your Expensify company account, so treating them with the same care as a password is essential. Store them in Replit Secrets, access them via os.environ or process.env, and never commit them to your Git history.

Integration method

Standard API Integration

You connect Replit to Expensify by registering for Integration Server credentials (partnerUserID and partnerUserSecret), storing them in Replit Secrets, and making POST requests to the Expensify Integration Server at integrations.expensify.com from your Python or Node.js backend. The API uses a JSON request payload posted as a form field named 'requestJobDescription', with credentials included in the payload itself rather than in HTTP headers. You can create expenses, submit reports, retrieve report data, and export expense lists to CSV.

Prerequisites

  • A Replit account with a Python or Node.js project created
  • An Expensify account with admin or domain admin access
  • Integration Server credentials from https://www.expensify.com/tools/integrations/
  • Basic familiarity with form-encoded HTTP POST requests and JSON payloads
  • Python 3.10+ or Node.js 18+ (both available on Replit by default)

Step-by-step guide

1

Obtain Expensify Integration Server Credentials

Go to https://www.expensify.com/tools/integrations/ while logged into your Expensify admin account. This page generates a unique partnerUserID and partnerUserSecret for your account. These credentials are tied to your Expensify email address and grant API access on behalf of your account. The credentials are shown on the page β€” copy both the partnerUserID (a string that looks like your email address or a generated ID) and the partnerUserSecret (a longer alphanumeric string). Unlike many APIs, Expensify does not allow regenerating these credentials via the web UI, so store them securely immediately. Note that the Integration Server API operates at the account level β€” all API calls you make will appear to come from the account associated with these credentials. If you need to create expenses on behalf of other employees in your company, you will need to ensure your account has domain admin access, which allows you to create expenses under other email addresses within the company domain.

Pro tip: If you are building integrations for a company domain, ensure the account used for Integration Server credentials has domain admin access so you can create expenses under any employee email in that domain.

Expected result: You have an Expensify partnerUserID and partnerUserSecret from the Integration Server page.

2

Store Credentials in Replit Secrets

Open your Replit project and click the lock icon πŸ”’ in the left sidebar to open the Secrets pane. Add two secrets: - Key: EXPENSIFY_USER_ID β€” Value: your partnerUserID - Key: EXPENSIFY_USER_SECRET β€” Value: your partnerUserSecret Click 'Add Secret' after each entry. These values are encrypted by Replit and injected as environment variables at runtime. Your source files and Git history will never contain the actual credential values. All Expensify API calls include these credentials inside the JSON request payload rather than in HTTP headers, so it is especially important to keep them out of code files where they might be inadvertently logged or committed.

Pro tip: The Expensify Integration Server API includes credentials inside the request body JSON, not in HTTP headers. Always read them from environment variables and build the payload at request time rather than constructing the payload in a constant.

Expected result: EXPENSIFY_USER_ID and EXPENSIFY_USER_SECRET appear in your Replit Secrets pane with values hidden.

3

Create Expenses and Retrieve Reports in Python

The Expensify Integration Server API has an unusual request format: you POST to https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations with the Content-Type application/x-www-form-urlencoded and a single form field called 'requestJobDescription' containing a JSON string with your credentials and the operation. The Python example below demonstrates three operations: creating an expense, creating a report, and fetching report details. Each request follows the same pattern of building a JSON payload and posting it as a URL-encoded form field. The API returns JSON responses for synchronous operations. For export operations (downloading large report lists), Expensify uses an asynchronous job pattern where you first request the export, get back a jobID, then poll for results.

expensify_client.py
1import os
2import json
3import requests
4from urllib.parse import urlencode
5
6USER_ID = os.environ["EXPENSIFY_USER_ID"]
7USER_SECRET = os.environ["EXPENSIFY_USER_SECRET"]
8
9API_URL = "https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations"
10
11
12def expensify_request(job_description: dict) -> dict:
13 """Make a request to the Expensify Integration Server."""
14 job_description["credentials"] = {
15 "partnerUserID": USER_ID,
16 "partnerUserSecret": USER_SECRET
17 }
18 payload = urlencode({"requestJobDescription": json.dumps(job_description)})
19 resp = requests.post(
20 API_URL,
21 data=payload,
22 headers={"Content-Type": "application/x-www-form-urlencoded"}
23 )
24 resp.raise_for_status()
25 return resp.json()
26
27
28def create_expense(employee_email: str, merchant: str, amount_cents: int,
29 currency: str, date: str, category: str = "") -> dict:
30 """Create a single expense for an employee. Amount is in cents (e.g. 2500 = $25.00)."""
31 job = {
32 "type": "create",
33 "dry-run": False,
34 "employeeEmail": employee_email,
35 "transactionList": [
36 {
37 "created": date, # YYYY-MM-DD
38 "currency": currency,
39 "merchant": merchant,
40 "amount": amount_cents,
41 "category": category,
42 "comment": f"Created via Replit API integration"
43 }
44 ]
45 }
46 return expensify_request(job)
47
48
49def get_report_list(employee_email: str, status_filter: str = "OPEN") -> dict:
50 """Get expense reports for an employee. status_filter: OPEN, SUBMITTED, APPROVED, REIMBURSED."""
51 job = {
52 "type": "get",
53 "employeeEmail": employee_email,
54 "reportState": status_filter,
55 "limit": "20"
56 }
57 return expensify_request(job)
58
59
60def export_report_csv(report_id_list: list) -> str:
61 """Export specific reports to CSV. Returns CSV string."""
62 job = {
63 "type": "download",
64 "onReceive": {"immediateResponse": ["returnRandomFileName"]},
65 "inputSettings": {
66 "type": "combinedReportData",
67 "filters": {
68 "reportIDList": ",".join(report_id_list)
69 }
70 },
71 "outputSettings": {"fileExtension": "csv"}
72 }
73 result = expensify_request(job)
74 # If async, result contains a jobID β€” poll for completion
75 if "jobID" in result:
76 print(f"Export queued with jobID: {result['jobID']}")
77 print("Poll for results: add jobID to a subsequent get request")
78 return result
79
80
81if __name__ == "__main__":
82 # Create a test expense
83 result = create_expense(
84 employee_email="employee@yourcompany.com",
85 merchant="Office Depot",
86 amount_cents=4599, # $45.99
87 currency="USD",
88 date="2026-03-15",
89 category="Office Supplies"
90 )
91 print("Create expense result:", result)
92
93 # Get open reports for the employee
94 reports = get_report_list("employee@yourcompany.com", status_filter="OPEN")
95 print("Open reports:", reports)

Pro tip: Expense amounts must be in the smallest currency unit (cents for USD). Pass 4599 for $45.99, not 45.99. Using floating-point dollar amounts will cause incorrect expense values.

Expected result: Running the script creates a test expense in Expensify and prints the list of open reports for the specified employee email.

4

Build a Node.js Expensify Integration

The Node.js implementation uses the same API structure. Install axios and the qs library for form encoding with 'npm install axios qs'. The qs library correctly serializes nested objects for URL-encoded form data. The Express server below exposes REST endpoints for common Expensify operations. Because the Expensify API uses form-encoded payloads with a JSON string inside, it is worth centralizing the request-building logic in a single helper function to avoid repetition and reduce the risk of credential injection errors.

server.js
1const express = require('express');
2const axios = require('axios');
3const qs = require('qs');
4
5const app = express();
6app.use(express.json());
7
8const USER_ID = process.env.EXPENSIFY_USER_ID;
9const USER_SECRET = process.env.EXPENSIFY_USER_SECRET;
10const API_URL = 'https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations';
11
12async function expensifyRequest(jobDescription) {
13 jobDescription.credentials = {
14 partnerUserID: USER_ID,
15 partnerUserSecret: USER_SECRET
16 };
17 const payload = qs.stringify({
18 requestJobDescription: JSON.stringify(jobDescription)
19 });
20 const { data } = await axios.post(API_URL, payload, {
21 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
22 });
23 return data;
24}
25
26// Create an expense
27app.post('/expense', async (req, res) => {
28 const { employee_email, merchant, amount_cents, currency, date, category } = req.body;
29 if (!employee_email || !merchant || !amount_cents) {
30 return res.status(400).json({ error: 'employee_email, merchant, and amount_cents are required' });
31 }
32 try {
33 const result = await expensifyRequest({
34 type: 'create',
35 'dry-run': false,
36 employeeEmail: employee_email,
37 transactionList: [{
38 created: date || new Date().toISOString().split('T')[0],
39 currency: currency || 'USD',
40 merchant,
41 amount: amount_cents,
42 category: category || '',
43 comment: 'Created via Replit API integration'
44 }]
45 });
46 res.json(result);
47 } catch (err) {
48 console.error('Create expense error:', err.message);
49 res.status(500).json({ error: err.message });
50 }
51});
52
53// Get reports for an employee
54app.get('/reports', async (req, res) => {
55 const { email, status = 'OPEN' } = req.query;
56 if (!email) return res.status(400).json({ error: 'email query param required' });
57 try {
58 const result = await expensifyRequest({
59 type: 'get',
60 employeeEmail: email,
61 reportState: status,
62 limit: '20'
63 });
64 res.json(result);
65 } catch (err) {
66 console.error('Get reports error:', err.message);
67 res.status(500).json({ error: err.message });
68 }
69});
70
71app.listen(3000, '0.0.0.0', () => {
72 console.log('Expensify integration server running on port 3000');
73});

Pro tip: Install the qs library (npm install qs) for reliable URL-encoding of the nested requestJobDescription form field. Node's built-in URLSearchParams may not handle the nested JSON string correctly for all Expensify endpoint types.

Expected result: The Node.js server starts and correctly creates expenses and returns report lists from the Expensify API.

5

Deploy and Test Your Integration

Deploy your Replit app by clicking the Deploy button. For expense processing triggered by user actions or incoming webhooks from other systems, use Autoscale deployment. If you are running a scheduled nightly job that exports reports, use a Replit Scheduled deployment or trigger the script from a cron-like mechanism within a Reserved VM. After deploying, log into your Expensify account and verify that test expenses created via the API appear in the correct employee's expense list. Check that amounts, merchants, dates, and categories match what your code sent. If you are building a report export pipeline, verify the CSV format of exported reports matches what your downstream system expects. Monitor for common issues in the Replit deployment logs: credentials being read incorrectly (undefined values in the JSON payload cause authentication failures), amount values being passed as floats instead of integers, and employee email addresses that don't match verified accounts in your Expensify domain.

Pro tip: Test with your own email address first before automating expense creation for other employees. Verifying that expenses appear correctly in your own Expensify account is faster than waiting for reports on a colleague's account.

Expected result: Your deployed Replit app successfully creates expenses visible in the Expensify web interface, and report exports produce correctly formatted data.

Common use cases

Automated Expense Import from Purchase Data

A Replit backend reads purchase records from a company database or third-party API (such as a corporate card feed), creates corresponding Expensify expenses via the API, and submits them to a report for the appropriate employee. This eliminates manual data entry for high-volume expense programs.

Replit Prompt

Build a Python Flask API that reads purchase records from a PostgreSQL database, creates Expensify expenses for each record under the employee's email address, and submits them to a monthly expense report using the Integration Server API.

Copy this prompt to try it in Replit

Export Reports to Internal Accounting System

A Replit scheduled job exports approved Expensify reports to a CSV or JSON format and posts the data to an internal accounting system or database. The job runs nightly, pulling all reports approved in the last 24 hours and synchronizing them with the general ledger.

Replit Prompt

Write a Python script that exports all approved Expensify expense reports from the last 7 days using the Integration Server export API, parses the CSV results, and stores the line items in a SQLite database for internal reporting.

Copy this prompt to try it in Replit

Expense Approval Status Dashboard

A Replit web app queries Expensify for the current approval status of all open expense reports across the company and displays them in a management dashboard. Finance managers can see at a glance which reports are pending, approved, or awaiting reimbursement without logging into Expensify directly.

Replit Prompt

Create an Express server that fetches all open expense reports from Expensify for a company domain, returns their titles, owners, amounts, and statuses, and renders them in a simple HTML table dashboard.

Copy this prompt to try it in Replit

Troubleshooting

Unauthorized error or credentials not accepted despite correct values in Secrets

Cause: The Expensify Integration Server embeds credentials inside the JSON request body, not in HTTP headers. If the JSON payload is malformed or the credentials object is missing, the request is rejected without a clear error message.

Solution: Print the full requestJobDescription JSON before sending to verify credentials are included correctly. Check that EXPENSIFY_USER_ID and EXPENSIFY_USER_SECRET are not undefined β€” verify they are set in Replit Secrets and restart the Repl after adding them.

typescript
1import json
2job = {"credentials": {"partnerUserID": USER_ID, "partnerUserSecret": USER_SECRET}, ...}
3print(json.dumps(job, indent=2)) # Verify structure before sending

Expense amounts appear incorrect or show wrong currency values in Expensify

Cause: Expensify expects expense amounts in the smallest currency unit (cents for USD). Passing a float dollar amount like 45.99 instead of the integer 4599 will create an expense worth a fraction of a cent.

Solution: Always convert dollar amounts to cents (integer) before passing to the API. Multiply the dollar amount by 100 and round to the nearest integer.

typescript
1amount_dollars = 45.99
2amount_cents = round(amount_dollars * 100) # 4599
3# Pass amount_cents to the API

Employee email not found or expenses appear under wrong account

Cause: The employee email used in the API call must be a verified member of your Expensify company domain. External email addresses or typos in the email will cause the expense to fail or appear under an unexpected account.

Solution: Verify the employee's email address is an active member of your Expensify domain. Log in to Expensify β†’ Admin β†’ Members to check the list of registered domain members. For the Integration Server to create expenses under other employees, your partnerUserID account must have domain admin access.

Export jobs return a jobID but result never becomes available

Cause: The Expensify export API is asynchronous for large datasets. The initial request returns a jobID, and you must poll a separate endpoint to check if the export has completed.

Solution: After receiving a jobID, poll the Integration Server with the jobID until the status changes from 'inProgress' to completed, then retrieve the download URL from the response.

typescript
1import time
2def poll_export(job_id: str, max_attempts=10) -> dict:
3 for _ in range(max_attempts):
4 result = expensify_request({"type": "get", "jobID": job_id})
5 if result.get("status") != "inProgress":
6 return result
7 time.sleep(5)
8 raise TimeoutError("Export did not complete in time")

Best practices

  • Always store EXPENSIFY_USER_ID and EXPENSIFY_USER_SECRET in Replit Secrets and read them via os.environ or process.env β€” never hardcode them in request payloads.
  • Pass expense amounts in the smallest currency unit (cents for USD, pence for GBP) β€” never use floating-point dollar amounts.
  • Use a domain admin account for Integration Server credentials if you need to create expenses on behalf of multiple employees across your company domain.
  • Validate employee email addresses against your Expensify domain member list before making API calls to avoid silent failures.
  • Implement polling logic for asynchronous export jobs β€” large report exports may take 10-30 seconds to complete and return a jobID rather than immediate results.
  • Log the full request structure (excluding credentials) when debugging unexpected API responses β€” the requestJobDescription format can be tricky to get right.
  • Use Autoscale deployment for on-demand expense APIs and Scheduled deployment (or a cron within Reserved VM) for nightly report exports.
  • Test all expense creation with a small set of real data in your own account before automating bulk imports for the whole company.

Alternatives

Frequently asked questions

How do I store Expensify credentials in Replit?

Click the lock icon πŸ”’ in the left sidebar to open the Secrets pane. Add EXPENSIFY_USER_ID with your partnerUserID and EXPENSIFY_USER_SECRET with your partnerUserSecret. Access them in Python with os.environ['EXPENSIFY_USER_ID'] and in Node.js with process.env.EXPENSIFY_USER_ID.

Where do I get Expensify Integration Server credentials?

Go to https://www.expensify.com/tools/integrations/ while logged in as an Expensify admin. The page generates and displays your partnerUserID and partnerUserSecret for the Integration Server API. These credentials are tied to your account and cannot be regenerated from the web UI, so save them securely.

Can I create expenses for other employees from Replit?

Yes, if your Expensify account has domain admin access. Set the 'employeeEmail' field in your API request to the employee's verified Expensify email address. The employee must be a registered member of your company domain in Expensify. Without domain admin access, you can only create expenses under your own account.

Does the Expensify API work on Replit free tier?

Yes, the API calls themselves work on Replit's free tier β€” there are no limitations on outbound HTTP requests. However, for production deployments that process expenses automatically or export reports on a schedule, you will need a paid Replit plan for always-on Reserved VM or Scheduled deployment.

How is the Expensify API different from standard REST APIs?

The Expensify Integration Server API uses HTTP POST requests with a single form field named 'requestJobDescription' containing a JSON string. All operations β€” creating expenses, fetching reports, exporting data β€” go to the same endpoint URL. Credentials are included inside the JSON payload rather than in HTTP headers, which is an unusual pattern that requires careful handling to avoid accidentally logging credentials.

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.