# How to Integrate Retool with Wave

- Tool: Retool
- Difficulty: Intermediate
- Time required: 25 minutes
- Last updated: April 2026

## TL;DR

Connect Retool to Wave by creating a GraphQL Resource pointing to Wave's API endpoint (https://gql.waveapps.com/graphql/public). Authenticate with OAuth 2.0 and build GraphQL queries in the Retool query editor to manage invoices, customers, and products — creating a free accounting dashboard for micro-businesses that need better reporting and operational tools than Wave's native interface provides.

## Why Connect Retool to Wave?

Wave's free accounting platform is popular with freelancers and micro-businesses, but its native reporting and operational UI has limitations — particularly for businesses that need custom financial views, invoice management across multiple clients, or integration of accounting data with other operational systems. Building a Retool Wave integration gives these businesses the operational control of a premium accounting tool's admin panel without the subscription cost.

With Wave connected to Retool, you can build an accounts receivable panel that shows all outstanding invoices sorted by overdue date, highlights invoices approaching payment deadlines, and lets finance staff send payment reminders without navigating Wave's interface for each client. You can create revenue dashboards that chart monthly invoiced amounts by customer or product, or build a customer management panel that combines Wave customer data with your own CRM notes stored in Retool Database.

Wave's GraphQL API differs from typical REST APIs — instead of fixed endpoints, you write flexible queries that request exactly the fields you need. Retool's GraphQL Resource handles this natively, providing schema introspection and auto-complete in the query editor. This means you can explore Wave's full data model (businesses, invoices, customers, products, accounts, transactions) directly in Retool and build precisely the queries your dashboard needs without consulting API documentation for every field name.

## Before you start

- A Wave account at https://www.waveapps.com with at least one business configured
- A Wave API application created at https://developer.waveapps.com/hc/en-us/sections/360001114072 with OAuth 2.0 credentials (Client ID and Client Secret)
- The Wave API OAuth 2.0 authorization URL (https://api.waveapps.com/oauth2/authorize) and token URL (https://api.waveapps.com/oauth2/token)
- A Retool account (Cloud or self-hosted) with permission to add Resources
- Basic familiarity with GraphQL query syntax (queries and mutations)

## Step-by-step guide

### 1. Create a Wave API application and obtain OAuth 2.0 credentials

Navigate to Wave's developer portal. Log into your Wave account at https://www.waveapps.com, then access the developer section through Wave's Help Center or directly at https://developer.waveapps.com. Look for the API Apps section or Applications dashboard — from here you can create a new API application.

Click Create Application (or similar, depending on the current Wave developer portal UI). Fill in the required fields:
- Application Name: e.g., 'Retool Dashboard'
- Homepage URL: your Retool instance URL (e.g., https://yourorg.retool.com)
- Redirect URI: for Retool Cloud, use https://oauth.retool.com/oauth/oauthcallback. For self-hosted Retool, use https://your-retool-instance.com/oauth/oauthcallback.

Once the application is created, Wave will display your Client ID and Client Secret. Note both of these carefully. Also note the OAuth 2.0 endpoints:
- Authorization URL: https://api.waveapps.com/oauth2/authorize
- Token URL: https://api.waveapps.com/oauth2/token

Wave's OAuth scopes for full access include: account:* (access account data), business:* (read/write business data), invoice:* (create and manage invoices), customer:* (manage customers). Check Wave's current scope documentation as available scopes may vary by plan.

Store your Client ID and Client Secret — you will use them in the Retool resource configuration in the next step.

**Expected result:** You have a Wave API Client ID, Client Secret, authorization URL, and token URL ready to configure in Retool.

### 2. Create a GraphQL Resource in Retool for Wave

Open Retool and go to the Resources tab in the sidebar. Click Add Resource. In the resource type search, type 'GraphQL' and select GraphQL from the list. This opens the GraphQL resource configuration form — distinct from REST API, as it supports schema introspection and type-aware query building.

Configure the resource:
- Name: 'Wave GraphQL API'
- Base URL: https://gql.waveapps.com/graphql/public (Wave's single GraphQL endpoint — all operations go to this URL)

For Authentication, select 'OAuth 2.0' from the dropdown. Fill in the OAuth 2.0 fields:
- Client ID: paste your Wave application's Client ID
- Client Secret: paste your Wave application's Client Secret
- Authorization URL: https://api.waveapps.com/oauth2/authorize
- Token URL: https://api.waveapps.com/oauth2/token
- Scopes: account:* business:* invoice:* customer:* (space-separated, adjust based on what Wave currently supports)
- Access Token Lifespan: set appropriately based on Wave's token expiry (check your Wave app settings or API documentation)

Scroll down and click Connect to OAuth (or Save and Connect). Retool will open a Wave OAuth consent screen in a popup where you log in and authorize the application. After authorization, Retool stores the access token and the resource is ready.

To verify the connection works, click the 'Test Connection' button if available, or create a test query below. Click Save Changes to finalize the resource.

**Expected result:** The Wave GraphQL API Resource appears in Retool's Resources list with an 'Authenticated' or 'Connected' status badge. The resource is ready to use in queries.

### 3. Build a GraphQL query to list Wave businesses and invoices

Wave's API requires you to first fetch your business ID, then use it to scope subsequent queries. Open or create a Retool app. In the Code panel, click + to create a new query. Select your Wave GraphQL API resource.

The query editor for a GraphQL resource shows a text editor where you write GraphQL query syntax, and optionally a Variables section for dynamic values. Write the following query to fetch your Wave businesses:

After saving and running this query, note the business ID from the response — you will use it in subsequent queries. For a production dashboard, store this as a Retool Select component that shows all businesses (in case you manage multiple), and let the user pick which business to query.

Now create a second query to fetch invoices for the selected business. Create a new query in the Code panel, select the Wave GraphQL resource, and write the invoices query. Use Retool's {{ }} syntax for the variable values — Retool passes these as the GraphQL variables object automatically when the query runs.

Drag a Table component onto the canvas. Set its Data to {{ invoicesQuery.data }}. Add a JavaScript transformer to the query to flatten the nested GraphQL response (GraphQL responses wrap data in a 'data' object, and Wave wraps results in edges/nodes structures for paginated collections).

```
# Query 1: Fetch Wave businesses
query GetBusinesses {
  businesses {
    edges {
      node {
        id
        name
        currency {
          code
        }
        isClassicAccounting
        isPersonal
      }
    }
  }
}

# Query 2: Fetch invoices for a business
query GetInvoices($businessId: ID!, $pageSize: Int, $status: InvoiceStatus) {
  business(id: $businessId) {
    invoices(
      page: 1
      pageSize: $pageSize
      status: $status
    ) {
      pageInfo {
        currentPage
        totalPages
        totalCount
      }
      edges {
        node {
          id
          invoiceNumber
          invoiceDate
          dueDate
          status
          total {
            value
            currency {
              code
            }
          }
          amountDue {
            value
          }
          customer {
            id
            name
          }
        }
      }
    }
  }
}
```

**Expected result:** The businesses query returns your Wave business IDs and names. The invoices query returns a list of invoices with status, amounts, customer names, and due dates for the selected business.

### 4. Transform GraphQL response and build an accounts receivable view

Wave's GraphQL responses follow a nested pattern: the data is wrapped in edges/nodes arrays (a Relay-style pagination pattern). Add a transformer to each query to flatten this structure into the simple arrays that Retool's Table and Chart components expect.

In your invoices query's Advanced tab, add a JavaScript transformer. Access the raw response through the 'data' variable in the transformer context.

Bind the transformer output to a Table component. Set the Table's Data to {{ invoicesQuery.data }}. Configure columns: invoiceNumber, customerName, totalValue, amountDue, invoiceDate, dueDate, status, and daysOverdue. For the daysOverdue column, set conditional row coloring: when daysOverdue > 0 use red background, when daysOverdue > 14 use dark red. Set the status column to use Tag rendering with colors: PAID → green, OUTSTANDING → yellow, OVERDUE → red, DRAFT → gray.

Add a revenue summary bar above the table. Use three Stat components showing: total outstanding (sum of amountDue where status OUTSTANDING), total overdue (sum where overdue), and invoiced this month (sum of totalValue where invoiceDate in current month). Bind these to JavaScript queries that aggregate invoicesQuery.data.

```
// Transformer to flatten Wave invoices GraphQL response
const response = data; // raw GraphQL response
const invoicesData = response?.business?.invoices?.edges || [];
const today = new Date();
return invoicesData.map(edge => {
  const inv = edge.node;
  const dueDate = inv.dueDate ? new Date(inv.dueDate) : null;
  const daysOverdue = dueDate && inv.status !== 'PAID'
    ? Math.max(0, Math.floor((today - dueDate) / (1000 * 60 * 60 * 24)))
    : 0;
  return {
    id: inv.id,
    invoiceNumber: inv.invoiceNumber || 'N/A',
    customerName: inv.customer ? inv.customer.name : 'Unknown',
    customerId: inv.customer ? inv.customer.id : null,
    totalValue: inv.total ? parseFloat(inv.total.value) : 0,
    amountDue: inv.amountDue ? parseFloat(inv.amountDue.value) : 0,
    currency: inv.total?.currency?.code || 'USD',
    invoiceDate: inv.invoiceDate ? new Date(inv.invoiceDate).toLocaleDateString() : 'N/A',
    dueDate: inv.dueDate ? new Date(inv.dueDate).toLocaleDateString() : 'N/A',
    status: inv.status || 'UNKNOWN',
    daysOverdue: daysOverdue,
    isOverdue: daysOverdue > 0
  };
});
```

**Expected result:** A Table displays Wave invoices with customer names, amounts, due dates, and color-coded status indicators. Overdue invoices appear at the top with red highlighting. KPI stat cards show outstanding balance and overdue totals.

### 5. Create and update Wave invoices using GraphQL mutations

Wave's GraphQL API supports mutations for creating and updating records. To let Retool users create new invoices, build a form that collects invoice details and submits a createInvoice mutation.

First, create a query to fetch Wave customers (for the customer selector in the invoice form):

Create a new query in the Code panel using your Wave GraphQL resource. Write a customers query that fetches all customers for the selected business. Add a transformer that flattens the response to an array of { id, name } objects.

Build the invoice form:
- Drag a Container component onto the canvas
- Inside it, add a Select for customer (options from customersQuery.data, label = name, value = id)
- Add a Date Picker for invoice date and due date
- Add a dynamic line items Table with an editable Amount and Description column (using Retool's editable Table mode)

Create a mutation query in the Code panel using the Wave GraphQL resource. Set the query to a GraphQL mutation that creates an invoice. Wire the 'Create Invoice' button to trigger this mutation query. In the mutation's Success handler, re-run the invoices query to refresh the table and show a success notification with the new invoice number.

For marking an invoice as paid, create a separate mutation query using Wave's patchInvoice mutation and wire it to a 'Mark Paid' button in the invoice detail panel. For complex multi-resource dashboards combining Wave with payment processors or CRM data, RapidDev's team can help architect your Retool solution.

```
# Fetch customers for invoice form
query GetCustomers($businessId: ID!) {
  business(id: $businessId) {
    customers(page: 1, pageSize: 200) {
      edges {
        node {
          id
          name
          email
        }
      }
    }
  }
}

# Mutation: Create a new Wave invoice
mutation CreateInvoice($input: InvoiceCreateInput!) {
  invoiceCreate(input: $input) {
    didSucceed
    inputErrors {
      code
      message
      field
    }
    invoice {
      id
      invoiceNumber
      status
    }
  }
}
```

**Expected result:** The invoice creation form allows selecting a customer, setting dates, and adding line items. Submitting the form creates a new invoice in Wave via the GraphQL mutation. The invoice table refreshes and shows the new invoice with DRAFT status.

## Best practices

- Always run the businesses query first to obtain your Wave business ID and store it in a Retool Select component or configuration variable — all subsequent Wave queries must scope their data to a specific business ID.
- Use GraphQL variables (the Variables section in Retool's query editor) rather than string interpolation in query bodies — this prevents GraphQL syntax errors and allows Retool to properly type-check the variable values.
- Add a transformer to every Wave query that checks for GraphQL errors in the response root (response.errors array) and surfaces them as Retool notifications, since GraphQL responses return HTTP 200 even when errors occur.
- Cache your customers and businesses queries with a long cache duration (30-60 minutes) since these datasets change infrequently — this reduces API calls on every form interaction without meaningful staleness risk.
- Use Wave's invoice status filter in your query (status: OUTSTANDING or status: OVERDUE) to limit the data fetched to only what the dashboard needs, rather than fetching all invoices and filtering client-side.
- For the accounts receivable view, create a dedicated query that fetches only overdue invoices (filter by status=OVERDUE) and bind it to a prominently placed urgent-action panel at the top of the dashboard.
- Store Wave configuration values (business IDs, default currency codes) in Retool configuration variables (Settings → Configuration Variables) so they can be updated by admins without modifying queries.

## Use cases

### Build an accounts receivable tracking dashboard

Create a Retool app that queries Wave for all outstanding invoices across your business, displaying client name, invoice amount, due date, days overdue, and payment status. Overdue invoices are highlighted in red using conditional row coloring. Finance managers can click an invoice to view its line items and mark it as paid by triggering a Wave mutation from a button — updating the invoice status without leaving Retool.

Prompt example:

```
Build an accounts receivable panel that queries Wave's invoices GraphQL endpoint for all unpaid and overdue invoices, displays them in a Table with columns for customer_name, invoice_number, amount_due, due_date, days_overdue, and status. Highlight rows where days_overdue > 0 in red. Add a 'Mark Paid' button that calls a Wave GraphQL mutation to update the invoice status.
```

### Monthly revenue and customer billing dashboard

Build a Retool financial dashboard that fetches Wave invoice data for the past 12 months and visualizes monthly revenue trends with a line chart. A table shows top customers by total invoiced amount and average payment time. The dashboard provides the revenue analytics that Wave's native reports only show as static PDFs, enabling real-time monitoring and custom date range analysis directly in Retool.

Prompt example:

```
Build a revenue dashboard that queries Wave invoices for the last 12 months via GraphQL, groups them by month and customer in a JavaScript transformer, and displays a line Chart of monthly_revenue over time. Add a Table showing top 10 customers by total_invoiced and average_days_to_pay. Include KPI stat cards for this_month_revenue, outstanding_balance, and overdue_amount.
```

### Invoice creation panel for operational teams

Create a Retool form-based app that lets operations team members create Wave invoices directly from Retool without Wave account access. The form pulls the customer list from Wave, lets the user add line items with product descriptions and amounts, and submits a GraphQL mutation to create the invoice in Wave. This makes invoice creation accessible to sales or project managers who should not have full Wave admin access.

Prompt example:

```
Build an invoice creation form that loads Wave customers from the GraphQL API into a Select dropdown. Add a dynamic line items table where users can add product descriptions, quantities, and unit prices. A 'Create Invoice' button constructs and submits a Wave createInvoice GraphQL mutation with the form data. On success, show the new invoice number and a link to view it in Wave.
```

## Troubleshooting

### OAuth 2.0 connection fails with 'invalid_client' or 'redirect_uri_mismatch' error

Cause: The redirect URI registered in your Wave API application does not match the OAuth callback URL that Retool uses. Wave's OAuth requires an exact match between registered and requested redirect URIs.

Solution: In your Wave developer application settings, verify the redirect URI is set to exactly https://oauth.retool.com/oauth/oauthcallback (for Retool Cloud) or https://your-retool-domain.com/oauth/oauthcallback (for self-hosted). The URLs must match character-for-character including trailing slashes. Update the Wave application if needed, then retry the OAuth connection in Retool.

### GraphQL queries return null for invoice or customer data even after successful authentication

Cause: Wave organizes data under a specific business ID. Queries that omit the business ID or use an incorrect one return null for nested business data. Each Wave account may have multiple businesses, and the query must target the correct one.

Solution: First run the GetBusinesses query to confirm your business ID and ensure your OAuth token has access to it. Then verify that your invoice and customer queries are passing the correct businessId variable. In the Retool query Variables section, explicitly set businessId to the correct ID value from the businesses query response.

### GraphQL mutations fail with 'didSucceed: false' and inputErrors

Cause: Wave's mutation responses use a pattern where didSucceed indicates success and inputErrors contains field-level validation errors when the mutation fails. The mutation query itself succeeds (HTTP 200) but the business logic validation failed.

Solution: Inspect the inputErrors array in the mutation response for specific error codes and messages. Common issues: missing required fields in the invoice input object (like businessId not included in the input), customer ID not found in the specified business, or date format mismatches. Add error handling in your Retool query's transformer to surface inputErrors to a notification when didSucceed is false.

```
// Check mutation success in a post-query transformer
const result = data.invoiceCreate;
if (!result.didSucceed) {
  const errors = result.inputErrors.map(e => e.message).join(', ');
  throw new Error('Wave mutation failed: ' + errors);
}
return result.invoice;
```

### Schema introspection returns no types or an empty schema in the GraphQL resource

Cause: Wave's GraphQL endpoint may require the Authorization header to be present even for introspection requests. If the OAuth token is not attached during the introspection call, the endpoint returns an unauthorized or empty schema.

Solution: Ensure the OAuth connection is fully established before attempting schema introspection (the resource shows a 'Connected' status). If introspection still fails, manually reference field names from Wave's developer documentation (https://developer.waveapps.com/hc/en-us/articles/360019968212) rather than relying on auto-complete. The queries still work correctly even without schema introspection.

## Frequently asked questions

### Is Wave's API free to use, or does it require a paid plan?

Wave's API is available to Wave users at no additional cost — Wave's core accounting features (invoicing, expense tracking, reporting) are free, and API access is included. You do need to create a developer application in Wave's developer portal to obtain OAuth 2.0 credentials. Wave's optional paid services (payroll, payments) are separate from API access.

### Why does Wave use GraphQL instead of a REST API?

Wave chose GraphQL to allow API consumers to request exactly the data they need in a single query, avoiding over-fetching (receiving unnecessary fields) or under-fetching (requiring multiple requests to assemble related data). In Retool, this means you can query invoices together with their line items, customer details, and payment history in a single query rather than making multiple REST calls and joining the results.

### Can I manage Wave invoices for multiple businesses from a single Retool dashboard?

Yes. First run the businesses query to fetch all businesses associated with your Wave account, then build a business selector dropdown in Retool. Each subsequent query passes the selected business ID as a variable. Since OAuth tokens are scoped to your Wave account (which may contain multiple businesses), a single OAuth connection handles all businesses your account manages.

### Does the Retool Wave integration support Wave Payments (collecting card payments)?

Wave Payments is a separate paid service integrated with Wave's invoicing. Via the GraphQL API, you can read the payment status of invoices and see payment method details, but initiating new payment link generation or processing payments programmatically requires Wave's Payments API capabilities — check Wave's current developer documentation for payment mutation support, as this varies by Wave's API version.

### How do I handle Wave's paginated GraphQL responses for large invoice lists?

Wave's GraphQL collections use page-based pagination with page and pageSize arguments plus a pageInfo object in the response containing currentPage, totalPages, and totalCount. In Retool, add pagination variables to your query (passing page as {{ pagination.page || 1 }} and pageSize as {{ pagination.pageSize || 50 }}) and bind a Retool Table's pagination controls to these variables. The Table will automatically trigger re-runs as the user navigates pages.

---

Source: https://www.rapidevelopers.com/retool-integrations/wave
© RapidDev — https://www.rapidevelopers.com/retool-integrations/wave
