Create expenses, reports and policies in Expensify, export report data, run card reconciliation, and update policies, tag approvers, expense rules and employees.
Expensify ships in the w6w first-party pack. It declares 15 actions, 3 health checks, and the host runs its code in a sandbox that never sees the credential.
io.w6w.expensifyExpensify is expense management and reimbursement software. This app drives its Integration Server: it creates expenses, reports, policies and expense rules, reads policies, policy details and domain card lists, exports report data through a Freemarker template and downloads the generated file, and runs card reconciliation exports.
It also updates a policy’s categories, tags and report fields, tag approvers and expense rules, marks approved reports as reimbursed, and provisions policy members through the Advanced Employee Updater.
Every call is one form-encoded job description posted to a single endpoint; the partner credentials are merged into it by the connection’s sign hook, never by an action. Health checks verify the credentials, whether the Integration Server is answering, Expensify’s status page (Integration APIs component), and declare that no rate-limit headroom signal exists.
Three routes to the same 15 actions. The Workflow tab is generated from Expensify's own manifest and carries its real ids, so it is copy-pasteable; the Code and CLI examples are the same call for any action on any app, so every app-specific value in them is a blank you fill in.
create-expense-rule Create an expense rule for a policy member: expenses they create get the given tag and/or billable default.
create-expenses Create expenses in the connected account (or, with advanced permissions, another employee's). Each expense needs merchant, created date, amount in cents and a currency.
create-report Create a report containing expenses in a policy. Domain and policy admin only, and the feature must first be enabled for your domain by concierge@expensify.com (otherwise 'Not authorized to authenticate as user').
download-file Download a file generated by Export Reports or Run Card Reconciliation. Text formats (csv, txt, json, xml) come back as `content`; binary formats (xlsx, xls, pdf) as `contentBase64`.
export-reports Generate a file of report and expense data from a Freemarker template, selected by report IDs or a date range. Returns the generated file name — pass it to Download File. Optionally marks the reports as exported and emails a link.
get-policy Fetch categories, report fields, tags, tax rates and/or employees for one or more policies.
list-domain-cards List the company cards assigned at domain level (masked number, owner, bank, last import).
list-policies List the account's policies with name, ID, type, owner, role and currency.
run-reconciliation Export card transactions for a domain and date range through a reconciliation template. Returns the generated file name — download it with Download File using file system `reconciliation`. Domain admin credentials only.
update-employees Provision and de-provision policy members from an employee feed: invite, set managers and roles, assign domain groups, remove terminated staff. Run with Dry run first.
update-expense-rule Update an existing expense rule of a policy member by rule ID.
update-policy Manage a policy's categories, tags and report fields from JSON. Updates one policy, or the same change across several policies. Needs admin rights on the policy.
update-report-status Mark Approved reports as Reimbursed, selected by report IDs or a date range. Reports in any other status are skipped. A partial result (207) is returned, not thrown, with the skipped and failed reports listed.
update-tag-approvers Route expenses carrying a given tag to a specific approver. Single-level tag lists only. Requires policy admin.
A workflow step names the app and the action, and the editor fills in the
connection when you pick one. This is the Step shape from the
workflow spec, carrying Expensify's real ids.
{
"manifestVersion": "2",
"name": "expensify-example",
"steps": [
{
"id": "create-expense-rule",
"uses": {
"app": "io.w6w.expensify",
"action": "create-expense-rule",
"connection": "conn_YOUR_CONNECTION_ID"
},
"with": {
"policyID": "<policyID>",
"employeeEmail": "<employeeEmail>"
}
}
]
}create-expense-rule create-expenses create-policy create-report download-file +10 more actions available
Every app-specific value here is a blank you have to fill in. An
app action is reached through the connection that authenticates it, so the
address is a connection id, not the app id — and connections belong to your account,
so a public page cannot know yours. Create one for Expensify, then fill in
the three blanks: conn_YOUR_CONNECTION_ID, the action key, and the
parameters that action declares. The call itself is real — the shape is transcribed
from the studio's own snippet builder, which prints the same kind of blanks — but
nothing in it is specific to Expensify. The Workflow tab is where this app's
real ids are.
npm install @w6w/sdkyarn add @w6w/sdkpnpm add @w6w/sdkdeno add npm:@w6w/sdkimport { W6wClient, isActionRun } from "@w6w/sdk";
// Reads W6W_BASE_URL and W6W_TOKEN from the environment when omitted.
const client = new W6wClient();
const envelope = await client.run({
urn: "conn_YOUR_CONNECTION_ID",
action: "create-expense-rule",
payload: {
policyID: "<value>",
employeeEmail: "<value>",
// tag: "<value>",
// defaultBillable: "<value>",
},
});
if (isActionRun(envelope)) console.log(envelope.value); npm install -g @w6w/cli w6w run conn_YOUR_CONNECTION_ID --action create-expense-rule --payload '{"policyID":"<value>","employeeEmail":"<value>"}' Give an AI agent Expensify — without giving it Expensify's credentials. One MCP endpoint exposes every app, function and workflow the caller is entitled to, as tools it can discover and run. Access is granted per team while we onboard.
One tool call{
"name": "w6w_invoke",
"arguments": {
"ref": "app:io.w6w.expensify#create-expense-rule",
"input": {
"policyID": "<policyID>",
"employeeEmail": "<employeeEmail>"
}
}
}
Every tool names its target with a single ref. The
app: form above doesn't name a connection at all — the
host resolves which of the caller's Expensify connections to sign
with, and refuses rather than guesses when the answer is ambiguous.
The token is attached host-side, at the moment of the call. It is never a tool argument, never in the model's context, and never in a transcript — so a prompt injection has nothing to exfiltrate.
Tools are derived per end user from what that person has actually connected and is entitled to — not one shared bot identity carrying the union of everyone's access.
Multi-step work runs on the workflow engine and returns a run handle the agent can poll — retries, branching and state survive the conversation that started them.
Expensify's declared health checks are on the surface too, so an agent can tell "the vendor is down" from "your credential expired" before it burns a retry on either.
The MCP surface is part of the hosted platform. Expensify itself is MIT, and the runtime that executes it is source-available (FSL).
Expensify declares its own checks, so its health is a property of the app rather than something the host guesses at.
status.expensify.com (Statuspage v2). The `Integration APIs` component decides; `Report PDFs` is capped at degraded; every other component is detail only.
Unauthenticated policyList job against integrations.expensify.com. Expensify's `{responseCode, responseMessage}` error envelope is the expected healthy answer; credential validity is the `auth:partner-credentials` check's job.