A reference guide for building AI agents: every method, how to authenticate, and the permissions each one needs.
The Expensify API is how an app or AI agent works with an Expensify account: exporting report and card data, creating expenses and reports, reading and updating policies, and marking reports reimbursed. Access is granted through a partner credential, a paired ID and secret generated per account, and the credential carries the account holder's own reach rather than a set of per-job scopes. Each request names a job type, and an export job can run on a schedule rather than the account pushing events back to an app.
How an app or AI agent connects to Expensify determines what it can reach. There is the Integration Server, which runs jobs against an account with a partner credential, and a hosted server that exposes Expensify search to AI agents, and each is governed by the credential behind it.
The Integration Server exposes one HTTP endpoint, ExpensifyIntegrations, that takes a form-encoded body with a requestJobDescription JSON object naming a job type (file, reconciliation, download, create, get, or update) plus a partner credential, input settings, and, for exports, a Freemarker template. It returns JSON with a responseCode and, on failure, a responseMessage. This is the route for creating, reading, updating, and exporting account data.
A first-party hosted Model Context Protocol server, launched June 2026 at expensify.com/mcp, connects AI assistants like Claude, ChatGPT, and Cursor over OAuth 2.1 with PKCE. It exposes a single Search tool that retrieves and analyses expenses, reports, reimbursements, invoices, merchants, categories, receipts, approvals, and spend trends. It is read-only and cannot create, edit, or delete data.
The Integration Server authenticates with a paired partnerUserID and partnerUserSecret, generated for the account at the tools page and sent inside the request body. There are no per-job scopes: the credential carries the full reach of the account it belongs to, so an admin credential can act across a domain while a member credential is limited to its own account. The secret is shown so it can be stored, and Expensify advises rotating it if it leaks.
The hosted MCP server authenticates with OAuth 2.1 with PKCE, where a member signs in with their existing Expensify account and grants the AI client access. The resulting access is read-only and tied to that member, separate from the partner credential used by the Integration Server.
The Integration Server groups work by job type: exporting report and reconciliation data, creating expenses, reports, and policies, reading policy and card details, and updating policies, employees, and report status. A single job can create or change many records at once.
Jobs that export report and card data and retrieve generated files.
Jobs that create expenses, reports, and policies.
Jobs that read policy details, the policy list, and domain cards.
Jobs that change policies, employees, expense rules, and report status.
Filter by method, access, or permission, or search any path. Select a row for version detail, rate limits, the related webhook event, and the source.
| Method | Endpoint | What it does | Access | Permission | Version | |
|---|---|---|---|---|---|---|
Export & downloadJobs that export report and card data and retrieve generated files.3 | ||||||
| POST | type=file | Export report and expense data to a file in a chosen format, shaped by a Freemarker template. | read | — | Current | |
No per-job scope; bounded by the account's reach. A template parameter is required to format the output. An onReceive setting controls whether the file is returned immediately or generated for later download. Acts onreport export Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=reconciliation | Export company-card transaction data for a date range and feed, as reported or all transactions. | read | — | Current | |
No per-job scope. Uses its own reconciliation template format, distinct from the report export template. Acts oncard reconciliation Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=download | Retrieve a file previously generated by an export job, by its filename. | read | — | Current | |
No per-job scope. Distinguishes between the reconciliation and integration-server file systems when locating the file. Acts ongenerated file Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
CreateJobs that create expenses, reports, and policies.4 | ||||||
| POST | type=create (expenses) | Create individual expenses in an account, with merchant, amount, date, category, and tag. | write | — | Current | |
No per-job scope; bounded by the credential's account. Supports assigning each expense a category, tag, and report. Acts onexpense Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=create (report) | Create an expense report with transactions in a user's account, returning a reportID. | write | — | Current | |
Requires the credential to be a domain and policy admin, and the capability enabled by contacting Concierge. Acts onreport Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=create (policy) | Create a new policy, optionally on a team or corporate plan. | write | — | Current | |
No per-job scope; bounded by the account's reach. Acts onpolicy Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=create (expenseRules) | Create automatic expense rules that tag or categorise an employee's expenses. | write | — | Current | |
No per-job scope; bounded by the account's reach. Acts onexpense rule Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
ReadJobs that read policy details, the policy list, and domain cards.3 | ||||||
| POST | type=get (policy) | Read a policy's configuration: categories, tags, report fields, tax rates, and employees. | read | — | Current | |
No per-job scope. Returns the full policy detail in one response. Acts onpolicy Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=get (policyList) | List the policies the account can see, with summaries like name, owner, role, type, and currency. | read | — | Current | |
No per-job scope. Supports an admin-only filter to limit results to policies the account administers. Acts onpolicy list Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=get (domainCardList) | List the company cards assigned at the domain level. | read | — | Current | |
No per-job scope; requires a domain-admin credential to see domain cards. Acts ondomain card list Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
UpdateJobs that change policies, employees, expense rules, and report status.6 | ||||||
| POST | type=update (policy) | Change a policy's categories, tags, and report fields, by merging or replacing them. | write | — | Current | |
No per-job scope. A merge action adds to existing values; a replace action overwrites them. Acts onpolicy Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=update (employees, Advanced) | Add, update, or remove policy members through the Advanced Employee Updater, with fields like department, country, and job code. | write | — | Current | |
The current way to provision employees; replaces the deprecated CSV employee updater. Automates policy assignment and manager invitations. Acts onemployee Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=update (employees) | Add or update policy members from a CSV. No longer maintained. | write | — | Current | |
Deprecated. Expensify directs integrators to the Advanced Employee Updater for adding, updating, or removing employees. Acts onemployee Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=update (expenseRules) | Update the automatic expense rules that tag or categorise an employee's expenses. | write | — | Current | |
No per-job scope; bounded by the account's reach. Acts onexpense rule Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=update (reportStatus) | Mark approved reports as reimbursed, by report ID list or date range, with an optional payment-source note. | write | — | Current | |
No per-job scope. Can return a 207 partial success with failed and skipped report lists. Acts onreport status Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | type=update (tagApprovers) | Assign approvers to individual tags within a policy. | write | — | Current | |
No per-job scope; bounded by the account's reach. Acts ontag approver Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
Expensify does not push events to an app. Instead, an export job can run on a schedule set in the account and deliver its file by email or to an SFTP destination when it finishes.
| Event | What it signals | Triggered by |
|---|
Expensify caps how fast an app can start jobs, counted over two windows, a short one and a longer one.
Expensify caps how fast an app can start jobs, counted over two windows: up to 5 requests every 10 seconds and up to 20 requests every 60 seconds. Going over returns HTTP 429, and the app should slow down and retry. The limits apply to job starts, not to the number of records a single job touches, so one create or export job can process many expenses or reports within a single request.
The Integration Server does not paginate with a cursor. A read job, like getting the policy list or a policy's details, returns the full result set in one response, and an export job filters by date range and report state rather than walking pages. To bound a large export, narrow the input filters.
An export or create job can carry many records in a single request, so the practical limit is the rate window rather than a fixed page size. A generated export file is held for later retrieval by the download job, named by the filename the export job returned.
The status codes an agent should handle, and what to do about each.
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 200 | Success | The job ran successfully. The response carries a responseCode of 200 and the job's result, like a reportID for a created report or a policyList for a list read. | Read the result fields from the response body. |
| 207 | Partial success | Some records in a batch succeeded and others failed, for example a report-status update where some reports were skipped. The response lists the failed and skipped records. | Inspect the failedReports or skipped arrays in the response, fix those records, and resubmit only them. |
| 404 | Policy not found | A policy named in the request, by policyID, does not exist or is not visible to the credential. | Confirm the policyID, which appears in the workspace URL, and that the credential's account can see that policy. |
| 410 | Validation error | The request was rejected for a validation problem, like a missing required field or a malformed value. The responseMessage names the issue. | Read the responseMessage, correct the request, and resend. The request is not retryable as-is. |
| 429 | Rate limit exceeded | Too many jobs were started too quickly, over the limit of 5 every 10 seconds or 20 every 60 seconds. | Slow the rate of job starts and retry with backoff. |
| 500 | Generic error | An error on Expensify's side prevented the job from completing. The responseMessage may carry detail. | Retry after a short wait, and contact Expensify support if it persists. |
The Integration Server is a single, continuously updated endpoint with no dated version in the path. Notable changes ship through Expensify product releases, like the hosted server for AI agents added in June 2026.
Expensify launched a first-party hosted Model Context Protocol server so AI assistants like Claude and ChatGPT can connect over OAuth 2.1 with PKCE and search Expensify data through a read-only Search tool. The Integration Server itself remains a single, unversioned endpoint updated in place.
The Integration Server has long exposed a single endpoint that runs export, create, read, and update jobs against an account, authenticated with a partner credential. Employee provisioning moved from the CSV employee updater to the Advanced Employee Updater, and the older updater is no longer maintained.
There is no version to pin; the Integration Server is updated in place.
Expensify Integration Server manual ↗Bollard AI sits between a team's AI agents and Expensify. Grant each agent exactly the access it needs, read or write, job by job, and every call is checked and logged.