Skip to main content
RapidDev - Software Development Agency
retool-integrationsREST API Resource

How to Integrate Retool with Expensify

Connect Retool to Expensify using a REST API Resource with your partnerUserID and partnerUserSecret as authentication credentials. Query expense reports, view individual expenses, and build approval workflow dashboards for finance teams — enabling report review and approval directly from Retool without requiring all team members to have full Expensify access.

What you'll learn

  • How to generate Expensify Integration Server credentials and configure them in Retool
  • How to query expense reports using Expensify's request-based API format
  • How to build an expense approval dashboard for finance team workflows
  • How to use JavaScript transformers to format expense data and calculate totals by category
  • How to trigger report status updates from Retool using Expensify's update report action
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate13 min read25 minutesOtherLast updated April 2026RapidDev Engineering Team
TL;DR

Connect Retool to Expensify using a REST API Resource with your partnerUserID and partnerUserSecret as authentication credentials. Query expense reports, view individual expenses, and build approval workflow dashboards for finance teams — enabling report review and approval directly from Retool without requiring all team members to have full Expensify access.

Quick facts about this guide
FactValue
ToolExpensify
CategoryOther
MethodREST API Resource
DifficultyIntermediate
Time required25 minutes
Last updatedApril 2026

Build Expense Approval and Reporting Dashboards in Retool with Expensify

Expensify's built-in approval workflows work well for standard linear approval chains, but finance teams often need custom views — cross-department expense comparisons, anomaly detection dashboards, bulk approval for routine expense types, or integration with accounting data from other systems. Retool addresses this by connecting directly to Expensify's API, enabling dashboards that surface the expense data your team needs in the context of other business data.

Expensify's Integration Server API uses a unique request format: all operations are POST requests to a single endpoint (expensify.com/Integration-Server/ExpensifyIntegrations), and the specific operation is defined by a JSON job description passed as a form parameter called 'requestJobDescription'. This differs from most REST APIs but provides consistent authentication — your partnerUserID and partnerUserSecret are credentials tied to your Expensify account that grant programmatic access.

Common Retool apps built on Expensify include: expense approval panels where managers review and approve reports submitted by their team, finance dashboards that aggregate expense totals by department, category, and time period, anomaly detection tools that flag expense reports with unusual amounts or categories compared to historical patterns, and expense export tools that pull approved reports and post transaction data to accounting systems already connected to Retool.

Integration method

REST API Resource

Expensify uses its Integration Server API with partnerUserID and partnerUserSecret credentials for authentication. These are passed as form parameters in POST requests to the Expensify API endpoint. Configure a Retool REST API Resource targeting the Expensify API, then build queries using the requestJobDescription parameter to specify operations. All requests are proxied server-side through Retool, keeping credentials secure.

Prerequisites

  • An Expensify account with admin or domain admin access
  • Expensify Integration Server credentials (partnerUserID and partnerUserSecret) from the Expensify Integration page
  • Your Expensify policy ID for the reports and expenses you want to query
  • A Retool account with permission to create Resources
  • Basic familiarity with form-encoded POST requests and JSON

Step-by-step guide

1

Generate Expensify Integration Server credentials

Log into your Expensify account with admin privileges. Navigate to expensify.com/tools/integrations — this is the Integration Server page accessible only to account admins. On this page you will see your partnerUserID (your Expensify email address formatted as a partner identifier) and the option to generate a partnerUserSecret. Click 'Generate' next to the partner user secret field. Expensify displays the secret once — copy it immediately. This is distinct from your Expensify login password; it is a dedicated API secret for Integration Server access. Both values together form the authentication credential for all API requests. If you need to restrict API access to specific reports or policies, note your policy ID from Expensify Settings → Policies → select policy → policy ID in the URL. Store both the partnerUserID and partnerUserSecret securely — anyone with these credentials can access your Expensify data programmatically.

Pro tip: Your Expensify partnerUserID is typically formatted as your company domain name (e.g., 'expensify.com' if your account email is user@expensify.com). This is different from your email login — check the Integration Server page to see the exact format used.

Expected result: You have an Expensify partnerUserID and partnerUserSecret copied and ready to configure in Retool, plus your policy ID noted.

2

Create the Expensify REST API Resource in Retool

In Retool, navigate to the Resources tab and click 'Create New'. Select 'REST API' from the resource type list. Set the Base URL to 'https://integrations.expensify.com'. This is the base URL for Expensify's Integration Server — all API calls go to this endpoint with the path '/Integration-Server/ExpensifyIntegrations'. Unlike most REST APIs, Expensify uses a single endpoint for all operations, with the specific operation defined in the request body. Under Authentication, leave it as 'None' because Expensify authenticates via form parameters in the request body rather than HTTP auth headers. Click 'Save Resource'. Note that all Expensify queries will use POST method with a 'multipart/form-data' or 'application/x-www-form-urlencoded' content type. Before saving, go to Retool Settings → Configuration Variables and add 'EXPENSIFY_USER_ID' and 'EXPENSIFY_SECRET' as secret variables for your credentials.

resource-config.json
1{
2 "Base URL": "https://integrations.expensify.com",
3 "Authentication": "None",
4 "Note": "Expensify authenticates via partnerUserID and partnerUserSecret in the POST body, not HTTP headers"
5}

Pro tip: Because Expensify uses body-based authentication rather than headers, the credentials are included in every query's request body as form parameters. Reference your configuration variables in each query: partnerUserID={{ retoolContext.configVars.EXPENSIFY_USER_ID }}.

Expected result: The Expensify REST API Resource is saved. Configuration variables for credentials are set up as secrets in Retool Settings.

3

Query expense reports with the Export Reports operation

Create a new query using the Expensify resource. Set the method to POST and the path to '/Integration-Server/ExpensifyIntegrations'. Set the body type to 'x-www-form-urlencoded'. Expensify's API requires two form parameters: 'partnerUserID' with your credential and 'partnerUserSecret' with your secret. The third parameter 'requestJobDescription' is a JSON string that specifies the operation. To export reports, use the 'Export' type with 'Expense' as the reportState. The requestJobDescription JSON includes the type 'Export', the credentials object, and an outputSettings object specifying the file format and what fields to include. For Retool dashboards, use 'json' as the format to get structured data back. The response for an export job includes a jobID — Expensify processes exports asynchronously, and you need to poll a second endpoint to retrieve results. For simpler real-time queries, use the 'Get' operation type which returns report data synchronously.

get-reports-query.json
1// POST body (x-www-form-urlencoded)
2// partnerUserID: {{ retoolContext.configVars.EXPENSIFY_USER_ID }}
3// partnerUserSecret: {{ retoolContext.configVars.EXPENSIFY_SECRET }}
4// requestJobDescription:
5{
6 "type": "get",
7 "credentials": {
8 "partnerUserID": "{{ retoolContext.configVars.EXPENSIFY_USER_ID }}",
9 "partnerUserSecret": "{{ retoolContext.configVars.EXPENSIFY_SECRET }}"
10 },
11 "inputSettings": {
12 "type": "reports",
13 "filters": {
14 "reportIDList": "",
15 "policyIDList": "{{ retoolContext.configVars.EXPENSIFY_POLICY_ID }}",
16 "startDate": "{{ dateRange.start || '2024-01-01' }}",
17 "endDate": "{{ dateRange.end || new Date().toISOString().split('T')[0] }}",
18 "approvedAfter": "",
19 "markedAsExported": "",
20 "statusFilters": ["Submitted", "Processing", "Approved"]
21 }
22 }
23}

Pro tip: The 'statusFilters' array controls which report states are returned. Use 'Submitted' to get reports awaiting approval, 'Approved' for approved reports, and 'Processing' for reports in multi-step approval chains. Omit the filter to return all reports regardless of status.

Expected result: The query returns a JSON object with a 'reportList' array containing expense reports matching your filters, each with report ID, submitter, total amount, currency, and status.

4

Transform expense data and build the approval table

Add a JavaScript transformer to the reports query to reshape the raw API response into a format suitable for Retool's Table component. The Expensify 'get' reports response returns a 'reportList' array where each report contains fields including 'reportID', 'reportName', 'total' (in cents — divide by 100 to get the dollar amount), 'currency', 'submitter' with user details, 'status', 'created', 'submitted', and 'managerEmail'. Transform this into a flat array with formatted currency values, readable date strings, and computed fields like days since submission. Add a Retool Table component bound to the transformer output. Configure action columns: an 'Approve' button that triggers an approval query, and a 'View Details' button that opens the report URL in Expensify. Add Stat components above the table showing total pending amount, number of reports awaiting approval, and average report value.

reports_transformer.js
1// Transformer: format expense reports for display
2const reports = data && data.reportList ? data.reportList : [];
3
4return reports.map(report => {
5 const totalDollars = report.total
6 ? (report.total / 100).toFixed(2)
7 : '0.00';
8 const submittedDate = report.submitted
9 ? new Date(report.submitted * 1000).toLocaleDateString('en-US', {
10 year: 'numeric', month: 'short', day: 'numeric'
11 })
12 : 'Not submitted';
13 const daysPending = report.submitted
14 ? Math.floor((Date.now() - report.submitted * 1000) / 86400000)
15 : null;
16
17 return {
18 report_id: report.reportID,
19 report_name: report.reportName || `Report ${report.reportID}`,
20 submitter: report.submitterEmail || 'Unknown',
21 total: `${report.currency || 'USD'} $${totalDollars}`,
22 total_raw: parseFloat(totalDollars),
23 status: report.status,
24 submitted_date: submittedDate,
25 days_pending: daysPending,
26 manager: report.managerEmail || 'Unassigned',
27 expense_count: report.expenses ? report.expenses.length : 0,
28 report_url: `https://www.expensify.com/reports?param={"param":"${report.reportID}"}`
29 };
30}).sort((a, b) => (b.days_pending || 0) - (a.days_pending || 0));

Pro tip: Expensify stores monetary amounts in cents (integer) to avoid floating point precision issues. Always divide by 100 to get dollar amounts. The currency field is a separate string (e.g., 'USD', 'EUR') — include it in your display to handle teams with multiple currencies.

Expected result: The Table displays all pending expense reports sorted by days awaiting approval, with submitter, total amount, and days pending. Summary stat components show aggregate metrics at the top of the dashboard.

5

Implement report approval and update actions

Create an approval query that uses Expensify's 'update' operation to change report status when the manager clicks the Approve button. The 'update' operation uses the same endpoint and authentication format as the 'get' operation, but with type 'update' in the requestJobDescription. To approve a report, set actionList to 'Approve' and provide the reportIDList with the selected report's ID. Add an event handler on the Table's Approve button column that runs this query with the selected row's report_id, then refreshes the main reports query. For rejection, build a Modal component that prompts for a rejection comment before triggering the 'update' operation with actionList 'Reject' and a comment included in the request. Add a confirmation dialog using Retool's built-in confirmation prompt on the Approve button to prevent accidental approvals. Use query event handlers to show success/failure notifications after each action using Retool's showNotification function.

approve-report-query.json
1// POST body for approval action
2// partnerUserID: {{ retoolContext.configVars.EXPENSIFY_USER_ID }}
3// partnerUserSecret: {{ retoolContext.configVars.EXPENSIFY_SECRET }}
4// requestJobDescription:
5{
6 "type": "update",
7 "credentials": {
8 "partnerUserID": "{{ retoolContext.configVars.EXPENSIFY_USER_ID }}",
9 "partnerUserSecret": "{{ retoolContext.configVars.EXPENSIFY_SECRET }}"
10 },
11 "inputSettings": {
12 "type": "reports",
13 "filters": {
14 "reportIDList": "{{ reportsTable.selectedRow.data.report_id }}"
15 }
16 },
17 "onReceive": {
18 "actionList": ["Approve"]
19 }
20}

Pro tip: The Expensify update operation with 'Approve' action triggers the same approval logic as clicking Approve in the Expensify UI, including notifying the submitter and moving the report through any configured multi-step approval chains. The approver identity is determined by the partnerUserID credential used.

Expected result: Clicking Approve on a report row triggers the update query, changes the report status in Expensify, shows a success notification, and refreshes the reports table to remove the newly approved report from the pending view.

Common use cases

Build a manager expense approval dashboard

Query all submitted expense reports awaiting approval for a specific manager or department. Display each report with the submitter name, total amount, submission date, number of expenses, and a quick summary of top spending categories. Allow managers to review and approve or reject reports directly from Retool with a comment, triggering an API call that updates the report status in Expensify without requiring the manager to log into Expensify separately.

Retool Prompt

Build a Retool expense approval dashboard showing all Expensify reports with status 'Submitted' for a selected approver. Display submitter, total amount, submission date, and top spending categories. Include an Approve button and a Reject button with a comment input that updates the report status in Expensify.

Copy this prompt to try it in Retool

Create a cross-department expense analysis panel

Pull all approved expense reports for the current quarter and aggregate spending by department, expense category, and employee. Use Retool Charts to visualize spending patterns — bar charts for department-level totals, pie charts for category breakdowns, and trend lines showing month-over-month changes. Combine Expensify data with budget data from your accounting system Resource to show actual vs. budgeted spending in one view.

Retool Prompt

Create a Retool quarterly expense analysis dashboard showing total approved spend by department and expense category for the current quarter, with bar charts for department comparison and a line chart showing weekly spending trends. Include filters for department and date range.

Copy this prompt to try it in Retool

Build an expense anomaly detection tool

Query all expense reports submitted in the last 30 days and apply JavaScript transformer logic to flag anomalies: expenses above configurable thresholds by category (e.g., meals over $100, transportation over $500), receipts with missing merchant information, duplicate submission amounts from the same employee, or expenses submitted on weekends. Present flagged items in a prioritized review table for the finance team to investigate before approving.

Retool Prompt

Build a Retool expense review tool that queries all submitted Expensify reports, applies anomaly detection rules (configurable thresholds per category), and displays flagged expenses in a Table sorted by risk level. Allow reviewers to mark items as reviewed or escalate for further investigation.

Copy this prompt to try it in Retool

Troubleshooting

API returns 'Invalid credentials' or authentication failed error

Cause: The partnerUserID or partnerUserSecret is incorrect, the credentials have been reset in Expensify, or the parameters are not being included correctly in the POST body.

Solution: Return to expensify.com/tools/integrations and verify your partnerUserID format — it is often not your email address but a formatted partner identifier shown on that page. If the secret has been regenerated since you configured Retool, update the EXPENSIFY_SECRET configuration variable with the new value. Ensure both parameters appear in the POST body as form-encoded fields, not as JSON or headers.

Report query returns empty reportList even though reports exist in Expensify

Cause: The policyIDList filter is targeting the wrong policy, the statusFilters array does not include the status of the reports you expect, or the date range is excluding the relevant reports.

Solution: First, test without any filters by removing startDate, endDate, and policyIDList from the request — this returns all accessible reports. If reports appear, re-add filters one at a time to identify which filter is excluding the expected results. Verify your policy ID by checking Expensify Settings → Policies → select policy → the ID appears in the URL as a hash string.

Expense amounts appear as very large numbers (e.g., 12500 instead of $125.00)

Cause: Expensify's API stores all monetary values in the minor currency unit — cents for USD. The 'total' field returns 12500 to represent $125.00, not dollars.

Solution: Always divide the 'total' field by 100 in your transformer before displaying. For currencies like Japanese Yen that have no minor unit, check the currency field — if it is 'JPY', do not divide by 100. Build currency-aware logic in your transformer if your team uses multiple currencies.

typescript
1// Currency-aware amount formatter
2const noMinorUnitCurrencies = ['JPY', 'KRW', 'VND', 'CLP', 'HUF'];
3const amount = noMinorUnitCurrencies.includes(report.currency)
4 ? report.total
5 : report.total / 100;
6return amount.toFixed(2);

Approval action returns success but the report status does not change in Expensify

Cause: The authenticated user (identified by the partnerUserID) is not the designated approver for the submitted report. Expensify approval actions only succeed when the credentials match the expected approver in the policy approval chain.

Solution: Verify that the partnerUserID used for API authentication matches the Expensify account designated as the approver for the policy the report is submitted under. In Expensify Settings → Policies → select policy → People, check the approver column for the submitter's role. The API user must be in the approval chain to take approval actions via the API.

Best practices

  • Store both partnerUserID and partnerUserSecret in Retool configuration variables as secrets — these credentials grant full programmatic access to your Expensify workspace and should never appear in frontend code
  • Add date range filters to all report queries — querying without date boundaries returns all historical reports and can result in very large response payloads that slow your Retool dashboard
  • Always divide the Expensify 'total' field by 100 in transformers immediately — storing the raw cents value in your table without conversion will confuse all users of the dashboard
  • Use Retool's confirmation prompts on approval and rejection buttons — financial approval actions are irreversible in many policy configurations, and accidental clicks can disrupt expense report workflows
  • Build a separate read-only view for expense exploration and a restricted approval view accessible only to managers — use Retool's role-based access controls to limit who can see the approval buttons and trigger status changes
  • Cache the policy list and user list queries for 5-10 minutes — these change rarely and caching reduces API calls on every dashboard load
  • For high-volume expense processing, use Retool Workflows with Expensify's export job approach (asynchronous export) rather than synchronous 'get' queries — the export approach handles large datasets more reliably and provides complete data including all expense line items

Alternatives

Frequently asked questions

Does Retool have a native Expensify connector?

No, Retool does not have a native Expensify connector. You connect using a REST API Resource with form-encoded POST requests to Expensify's Integration Server API. The integration requires passing your partnerUserID and partnerUserSecret in every request body rather than in HTTP headers, which is unique to Expensify's API design.

Can I view individual expense line items, not just report totals?

Yes. When querying reports with the 'get' operation, include 'expenses' in the outputSettings fields. Each report object in the response will contain an 'expenses' array with individual line items showing merchant, amount, category, date, receipt URL, and comment. You can display these in a detail panel that opens when a user clicks a report row in your main table.

How do I get expense data for a specific employee rather than the whole company?

Include the 'employeeEmail' field in the filter object of your requestJobDescription. Set it to the email address of the specific employee whose reports you want to query. This returns only reports submitted by that employee under the specified policy. For a manager dashboard, populate a Select dropdown with employee email addresses from your HR system and bind its value to this filter parameter.

Are there rate limits on the Expensify Integration Server API?

Expensify enforces rate limits on Integration Server API calls, though the specific limits are not publicly documented. For dashboard use cases with queries refreshing every few minutes, limits are rarely an issue. For high-volume automated processing (such as exporting thousands of reports in a Workflow), implement delays between requests and handle errors gracefully with retry logic.

Can I create expense reports programmatically from Retool?

Yes. Use the 'create' operation type in your requestJobDescription with an 'expenses' array containing expense objects (merchant, amount, category, date, currency). You can also attach report metadata like report name and policy. This is useful for building tools that automatically create expense reports from corporate card transaction data or other financial systems connected to Retool.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire Retool integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

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.