ml-connector
AcumaticaExpensify

Acumatica and Expensify integration

Acumatica runs your finance and ERP records. Expensify runs employee expense capture, approval workflows, and corporate card reconciliation. Connecting the two posts the spend employees claim in Expensify straight into Acumatica's ledger without re-keying. Approved and reimbursed expense reports flow into Acumatica as AP Bills or GL journal entries, each line coded to the matching GL account, while employees and the accounts that drive coding stay aligned across both systems. ml-connector handles the very different APIs on each side and moves data on a schedule you control.

How Acumatica works

Acumatica exposes vendors, AP bills, purchase orders, payments, GL accounts, journal transactions, employees, and items through its Contract-Based REST API. Each instance has a tenant-specific base URL and a version-locked endpoint path, where the version in the URL must exactly match the running ERP release or the call returns 404. It authenticates with OAuth 2.0 through the built-in OpenID Connect server, or with a legacy session cookie. Every field value in a request or response is wrapped as a value object, and there is no native idempotency key. Acumatica can push events through its Push Notifications system using a shared-secret header rather than HMAC, but most integrations poll using a LastModifiedDateTime filter with top and skip offset pagination.

How Expensify works

Expensify exposes expense reports, individual expenses, employees, and policies through its Integration Server API. Every request is an HTTP POST to a single URL, where a type field inside the JSON body selects the operation, and there are no RESTful resource paths or GET parameters. It authenticates with a long-lived partnerUserID and partnerUserSecret pair passed inline on each call, with no OAuth tokens to refresh. Expensify is pull-only, with no event push, so connectors poll for reports filtered by approvedAfter and markedAsExported. It has no vendor, invoice, purchase order, or payment entity, GL codes live as a categoryGLCodeMap attached to policy categories, and expense amounts are always returned in cents.

What moves between them

The main flow runs from Expensify into Acumatica. As reports reach an approved or reimbursed state in Expensify, ml-connector exports them and posts each expense line into Acumatica, as an AP Bill or a GL journal entry, mapped by Expensify category to the matching Acumatica GL account and subaccount. Tags such as project, department, or class are carried onto the posting as Acumatica dimensions. Reference data flows the other direction: Acumatica employees are provisioned into the right Expensify policies so approval chains and expense submitters stay current, and GL account codes are aligned so categories map to accounts that exist in the ledger. Expensify holds no invoices or payments, so the connector posts expense data into Acumatica and never expects AP records back from Expensify. Cadence is scheduled polling because Expensify does not push.

How ml-connector handles it

ml-connector stores both credential sets encrypted, obtains an Acumatica OAuth token and refreshes it on a 401, and sends the static Expensify partnerUserID and partnerUserSecret inline on every export request. On the Acumatica side it pins the endpoint version per customer so the version-locked URL resolves, and it wraps every field value in the required value object so writes do not return 400. Each Expensify category is mapped through its categoryGLCodeMap to an Acumatica GL account, and cent amounts are converted to currency before posting so totals match. Because Acumatica has no idempotency key, the connector carries each Expensify reportID as the vendor reference and checks for an existing document before creating one, and on the Expensify side it calls markAsExported after a successful post and filters subsequent polls on markedAsExported so a report is never ingested twice. It polls on approvedAfter against the last high-water mark rather than waiting for a push. Acumatica returns 429 when the past-minute request count crosses the licensed threshold and Expensify caps at five requests per ten seconds, so ml-connector backs off with jitter and retries within both ceilings. Workspaces must be Expensify Classic, since New Expensify exposes a more limited API. Every record carries a full audit trail and can be replayed if a downstream call fails.

A real-world example

A mid-sized professional services firm of about 300 staff runs Acumatica for its general ledger and AP, and rolled out Expensify so consultants can capture travel and client expenses from their phones with a manager approval step. Before the integration, the finance team exported approved expense reports from Expensify each week, converted the totals, and re-keyed them into Acumatica line by line, mapping each category to a GL account by hand and chasing employees whose Expensify profile did not match the ledger. With Acumatica and Expensify connected, approved reports post into Acumatica automatically with each line coded to the right account and project, employees are provisioned from Acumatica into the correct policies, and reimbursable spend is reconciled by category. The weekly re-keying step is gone and month-end expense accruals start clean.

What you can do

  • Post approved and reimbursed Expensify expense reports into Acumatica as AP Bills or GL journal entries.
  • Map each Expensify category to the matching Acumatica GL account, and tags to ledger dimensions.
  • Provision Acumatica employees into Expensify policies so submitters and approval chains stay current.
  • Bridge Acumatica OAuth with the static Expensify partnerUserID and partnerUserSecret key pair, version-pinning each call.
  • Convert cent amounts to currency and gate posting on report state, with retries and a full audit trail on every record.

Questions

Which direction does data move between Acumatica and Expensify?
The main flow is Expensify into Acumatica. Approved and reimbursed expense reports move from Expensify into Acumatica as AP Bills or GL journal entries, coded by category to the right GL accounts. Employees are provisioned the other direction, from Acumatica into Expensify policies, and Expensify holds no invoices or payments, so the connector never expects AP records back from it.
How does the integration bridge the two different auth models?
Acumatica uses OAuth 2.0 through its built-in OpenID Connect server, while Expensify uses a static partnerUserID and partnerUserSecret pair sent inline on every request. ml-connector stores both encrypted, refreshes the Acumatica token on a 401, and passes the Expensify credentials with each export call. Because Expensify has no token to refresh, the secret is rotated in its portal and updated in the connector when it changes.
How are duplicate postings avoided when Expensify cannot push events?
Expensify is pull-only, so ml-connector polls on approvedAfter against a stored high-water mark to pick up newly approved reports. After a report posts successfully into Acumatica, the connector calls markAsExported and filters later polls on markedAsExported so the same report is never ingested twice. Because Acumatica has no idempotency key, it also carries the Expensify reportID as the document reference and checks for an existing record before creating one.

Related integrations

Connect Acumatica and Expensify

Free to use. Add your credentials, ping your real systems, and see if we fit.

Get started