ml-connector
Wave AccountingAnaplan

Wave Accounting and Anaplan integration

Wave Accounting runs the books for a small business: invoicing, the chart of accounts, and journal entries. Anaplan runs financial planning, budgeting, and forecasting. Connecting the two means the actuals recorded in Wave feed the planning models in Anaplan without manual file exports, and the budget figures approved in Anaplan can post back into Wave as journal entries. ml-connector handles the very different APIs on each side and moves the data on a schedule you control. Because Anaplan has no native accounting objects, the connector treats Wave as the system of record and shapes everything into Anaplan lists and modules.

How Wave Accounting works

Wave Accounting exposes all accounting data through a single GraphQL endpoint at gql.waveapps.com, where reads are queries and writes are mutations. It authenticates with the OAuth2 authorization code flow, issuing bearer access tokens that expire in about two hours, with a refresh token when offline_access is granted. Every query and mutation is scoped to a business id obtained from the businesses query after login. Key entities include customers, invoices, products, chart-of-accounts GL accounts, and money-transaction journal entries. Wave supports signed webhooks for events like invoice.created and transaction.created, but list reads use offset-based page and pageSize pagination, and the connected business must hold a Wave Pro subscription for third-party access.

How Anaplan works

Anaplan is a planning platform that exposes data through its REST Integration API v2.0 at api.anaplan.com, using JSON and CSV. Everything lives inside a workspace and model, so nearly every call carries a lowercase-hex workspace id and an uppercase-hex model id. Authentication uses Basic auth or a certificate to mint an AnaplanAuthToken that lives 35 minutes, or OAuth2 authorization code or device grant, since client credentials are not supported. Finance data enters Anaplan as list members and module cells through named Import actions, and planned values leave through Export actions, both run asynchronously by polling a task id to completion. Anaplan has no native invoice, vendor, or GL account object and sends no outbound webhooks.

What moves between them

The main flow runs from Wave Accounting into Anaplan. ml-connector reads Wave GL accounts and posts them as members of an Anaplan accounts list, then loads journal-entry transactions and invoice totals into a planning module as actuals through a named Import action, with Wave customers loaded as a list where the model plans by customer. A second flow runs the other direction: once a budget is approved in Anaplan, the connector runs an Export action, downloads the result, and writes the figures into Wave as journal-entry transactions. Cadence is scheduled on both sides, typically a periodic actuals load into Anaplan and a planned writeback into Wave after each planning cycle, since neither system pushes events that fit this pair.

How ml-connector handles it

ml-connector stores both credential sets encrypted. On the Wave side it sends the OAuth2 bearer token on every GraphQL request and refreshes it before the roughly two-hour expiry rather than on a 401, and it resolves the business id once and reuses it. On the Anaplan side it mints an AnaplanAuthToken from Basic auth or presents the OAuth token, reusing it across the 35-minute window, and supplies the lowercase workspace id and uppercase model id on each call. Loads into Anaplan run as named Import actions: the connector uploads the data file in chunks, starts the import, and polls the task id until the state is COMPLETE, because a 200 on the POST does not mean the data is in. Wave GL accounts are mapped to Anaplan list members first, so every actuals line resolves to a member that already exists. Writebacks into Wave use the moneyTransactionCreate mutation with a stable externalId derived from the source plan line, which dedupes journal entries on retry since Wave has no idempotency header elsewhere. Real edge cases are handled directly: Anaplan locks the model during an import or export so the connector serializes its actions against the same model, GraphQL errors come back with HTTP 200 so it checks the errors array rather than the status code, the Anaplan tenant-wide 600 requests per minute and Wave 429s both get exponential backoff with jitter, and the import action must already exist in the model because the API runs existing actions and cannot create them. A Wave webhook such as transaction.created, verified by its HMAC-SHA256 x-wave-signature, can trigger an earlier load, but the schedule remains the backbone. Every record carries a full audit trail and can be replayed if a downstream call fails.

A real-world example

A growing professional-services firm of about forty people runs its books in Wave Accounting and has outgrown spreadsheet budgeting, so it adopts Anaplan for financial planning. Before the integration, a controller exported Wave transactions to CSV every month and hand-keyed the account totals into the Anaplan budget model, which delayed the forecast and introduced typing errors that made the plan-versus-actual variance untrustworthy. With Wave Accounting and Anaplan connected, each period's GL accounts and journal entries load into the planning module automatically against the matching list members, and the budget the leadership team approves in Anaplan posts back into Wave as journal entries. The monthly export-and-rekey step is gone, and the forecast compares against actuals that tie to the ledger.

What you can do

  • Load Wave Accounting GL accounts into Anaplan as list members so every actuals line resolves to a real account.
  • Post Wave journal entries and invoice totals into an Anaplan planning module as actuals through a named Import action.
  • Write approved Anaplan plan figures back into Wave as journal-entry transactions keyed on a stable externalId.
  • Bridge Wave OAuth2 bearer tokens with the 35-minute Anaplan auth token and refresh each before it expires.
  • Run Anaplan imports and exports asynchronously by polling the task id, with model-lock serialization, retries, and a full audit trail on every record.

Questions

Which direction does data move between Wave Accounting and Anaplan?
Both directions are supported, with Wave Accounting as the system of record. Actuals move from Wave into Anaplan: GL accounts become list members and journal entries and invoice totals load into a planning module through an Import action. Approved plan figures move the other way, written back into Wave as journal-entry transactions through the moneyTransactionCreate mutation.
Does Anaplan store invoices and accounts the way Wave does?
No. Anaplan is a planning platform and has no native invoice, vendor, or GL account object. Instead it holds lists, modules, and line items that a model builder configures, so ml-connector maps Wave GL accounts to list members and loads journal-entry and invoice data into module cells rather than creating named accounting records. The required Import and Export actions must already exist in the Anaplan model.
How does the integration sync when neither system offers a usable push for this pair?
It polls on a schedule. Anaplan sends no outbound webhooks at all, and its bulk imports and exports are asynchronous, so the connector starts an action and polls the task id until it completes. Wave does sign webhooks such as transaction.created, verified by HMAC-SHA256, which can trigger an earlier load, but the scheduled poll remains the backbone and serializes actions so an Anaplan model lock is never hit by a concurrent run.

Related integrations

Connect Wave Accounting and Anaplan

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

Get started