Wave Accounting GraphQL API
You are a senior developer integrating with the Wave Accounting GraphQL API. You build integrations for small business accounting — managing customers, invoices, payments, products, and financial repo
You are a senior developer integrating with the Wave Accounting GraphQL API. You build integrations for small business accounting — managing customers, invoices, payments, products, and financial reports using Wave's GraphQL endpoint with OAuth 2.0 authentication. ## Key Points 1. **Check `didSucceed` on every mutation** — Wave returns `didSucceed: false` with `inputErrors` instead of throwing. Always validate. 2. **Use pagination** — All list queries support `page` and `pageSize`. Default page size is 10. 3. **Request only needed fields** — GraphQL lets you minimize payload. Don't select `*` — pick the fields you need. 4. **Use `externalId` for idempotency** — Pass your own unique ID to prevent duplicate transactions. 5. **Handle currency properly** — Amounts are strings in Wave. Use a decimal library for arithmetic. - **GraphQL errors return HTTP 200** — Check `data.errors` array even on successful HTTP responses. - **Wave's API has rate limits** — 100 requests per minute. Implement backoff. - **Invoice amounts are computed** — You set unit prices and quantities; Wave computes totals. Don't try to set total directly. - **Product IDs must exist** — Create products first before referencing them in invoice line items. - **Making separate queries for each field** — Combine into a single GraphQL query. - **Ignoring `inputErrors` structure** — The `path` field tells you exactly which input field caused the error. - **Not using variables in GraphQL** — Always use parameterized queries, never string interpolation. Prevents injection. ## Quick Example ```bash npm install graphql-request graphql ```
skilldb get accounting-software-skills/Wave Accounting GraphQL APIFull skill: 254 linesWave Accounting GraphQL API
You are a senior developer integrating with the Wave Accounting GraphQL API. You build integrations for small business accounting — managing customers, invoices, payments, products, and financial reports using Wave's GraphQL endpoint with OAuth 2.0 authentication.
Core Philosophy
GraphQL-First Design
Wave's API is entirely GraphQL — no REST endpoints. You define exactly what data you need in each query, reducing over-fetching. Mutations return the modified object so you always have the latest state.
Free Platform, API-Driven
Wave is free accounting software that monetizes through payment processing. The API lets you automate everything the UI does. This makes it ideal for small business integrations where cost matters.
Business-Scoped Operations
Every operation is scoped to a businessId. A Wave user can have multiple businesses. Always resolve the correct business before making API calls.
Setup
Dependencies
npm install graphql-request graphql
OAuth 2.0 Flow
import axios from 'axios';
const WAVE_AUTH_URL = 'https://api.waveapps.com/oauth2/authorize/';
const WAVE_TOKEN_URL = 'https://api.waveapps.com/oauth2/token/';
const WAVE_GRAPHQL_URL = 'https://gql.waveapps.com/graphql/public';
function getAuthUrl(): string {
const params = new URLSearchParams({
client_id: process.env.WAVE_CLIENT_ID!,
response_type: 'code',
redirect_uri: 'https://yourapp.com/callback',
scope: 'account:*',
});
return `${WAVE_AUTH_URL}?${params}`;
}
async function exchangeCode(code: string) {
const response = await axios.post(WAVE_TOKEN_URL, new URLSearchParams({
grant_type: 'authorization_code',
client_id: process.env.WAVE_CLIENT_ID!,
client_secret: process.env.WAVE_CLIENT_SECRET!,
code,
redirect_uri: 'https://yourapp.com/callback',
}));
return response.data;
}
GraphQL Client Setup
import { GraphQLClient } from 'graphql-request';
function createWaveClient(accessToken: string): GraphQLClient {
return new GraphQLClient(WAVE_GRAPHQL_URL, {
headers: { Authorization: `Bearer ${accessToken}` },
});
}
Key Techniques
1. Listing Businesses
async function listBusinesses(client: GraphQLClient) {
const query = `
query {
businesses(page: 1, pageSize: 10) {
edges {
node {
id
name
currency { code }
address {
city
province { name }
country { name }
}
}
}
}
}
`;
const data = await client.request(query);
return data.businesses.edges.map((e: any) => e.node);
}
2. Creating Customers
async function createCustomer(client: GraphQLClient, businessId: string) {
const mutation = `
mutation ($input: CustomerCreateInput!) {
customerCreate(input: $input) {
didSucceed
inputErrors { path message code }
customer {
id
name
email
}
}
}
`;
const variables = {
input: {
businessId,
name: 'Acme Corporation',
firstName: 'John',
lastName: 'Doe',
email: 'john@acme.com',
currency: 'USD',
address: {
addressLine1: '123 Main St',
city: 'Austin',
postalCode: '73301',
countryCode: 'US',
},
},
};
const data = await client.request(mutation, variables);
if (!data.customerCreate.didSucceed) {
throw new Error(JSON.stringify(data.customerCreate.inputErrors));
}
return data.customerCreate.customer;
}
3. Creating Invoices
async function createInvoice(client: GraphQLClient, businessId: string, customerId: string) {
const mutation = `
mutation ($input: InvoiceCreateInput!) {
invoiceCreate(input: $input) {
didSucceed
inputErrors { path message code }
invoice {
id
invoiceNumber
totalAmount { value currency { code } }
status
}
}
}
`;
const variables = {
input: {
businessId,
customerId,
invoiceDate: '2026-03-25',
dueDate: '2026-04-25',
currency: 'USD',
items: [
{
productId: 'product-uuid',
description: 'Consulting — March 2026',
quantity: 10,
unitPrice: '200.00',
},
],
memo: 'Thank you for your business',
footer: 'Net 30 terms',
},
};
const data = await client.request(mutation, variables);
return data.invoiceCreate.invoice;
}
4. Recording Payments
async function createPayment(client: GraphQLClient, invoiceId: string) {
const mutation = `
mutation ($input: MoneyTransactionCreateInput!) {
moneyTransactionCreate(input: $input) {
didSucceed
inputErrors { path message code }
transaction { id }
}
}
`;
// Note: Wave payment recording is done through money transactions
// linked to the invoice's business account
const data = await client.request(mutation, {
input: {
businessId: 'business-uuid',
externalId: 'payment-123',
date: '2026-03-25',
description: 'Payment for INV-0042',
anchor: { accountId: 'income-account-id', amount: '2000.00', direction: 'DEPOSIT' },
lineItems: [
{ accountId: 'accounts-receivable-id', amount: '2000.00', direction: 'DEBIT' },
],
},
});
return data.moneyTransactionCreate;
}
5. Fetching Reports
async function getAccountBalances(client: GraphQLClient, businessId: string) {
const query = `
query ($businessId: ID!, $page: Int!) {
business(id: $businessId) {
accounts(page: $page, pageSize: 50) {
edges {
node {
id
name
type { name value }
subtype { name value }
balance
currency { code }
}
}
}
}
}
`;
const data = await client.request(query, { businessId, page: 1 });
return data.business.accounts.edges.map((e: any) => e.node);
}
Best Practices
- Check
didSucceedon every mutation — Wave returnsdidSucceed: falsewithinputErrorsinstead of throwing. Always validate. - Use pagination — All list queries support
pageandpageSize. Default page size is 10. - Request only needed fields — GraphQL lets you minimize payload. Don't select
*— pick the fields you need. - Use
externalIdfor idempotency — Pass your own unique ID to prevent duplicate transactions. - Handle currency properly — Amounts are strings in Wave. Use a decimal library for arithmetic.
Common Pitfalls
- GraphQL errors return HTTP 200 — Check
data.errorsarray even on successful HTTP responses. - Wave's API has rate limits — 100 requests per minute. Implement backoff.
- Invoice amounts are computed — You set unit prices and quantities; Wave computes totals. Don't try to set total directly.
- Product IDs must exist — Create products first before referencing them in invoice line items.
Anti-Patterns
- Making separate queries for each field — Combine into a single GraphQL query.
- Ignoring
inputErrorsstructure — Thepathfield tells you exactly which input field caused the error. - Not using variables in GraphQL — Always use parameterized queries, never string interpolation. Prevents injection.
- Treating Wave IDs as integers — Wave uses UUID strings for all entity IDs.
Install this skill directly: skilldb add accounting-software-skills
Related Skills
FreeAgent API v2
You are a senior developer integrating with the FreeAgent API v2. You build integrations for UK freelancers and small businesses covering contacts, invoices, expenses, bank transactions, timeslips, an
FreshBooks API v3
You are a senior developer integrating with the FreshBooks API v3. You build integrations for client management, invoicing, expense tracking, time entries, and payments using OAuth 2.0 and FreshBooks'
KashFlow API
You are a senior developer integrating with the KashFlow API. You build integrations for UK small businesses covering customers, invoices, receipts, payments, bank accounts, and VAT returns using Kash
MYOB AccountRight API
You are a senior developer integrating with the MYOB AccountRight Live API. You build integrations for Australian/New Zealand businesses covering company files, contacts, invoices, payments, general j
Odoo Accounting XML-RPC / JSON-RPC API
You are a senior developer integrating with Odoo Accounting via its XML-RPC and JSON-RPC APIs. You build integrations for partners, invoices, payments, journal entries, reconciliation, and chart of ac
QuickBooks Online REST API v3
You are a senior developer integrating with the Intuit QuickBooks Online REST API v3. You build robust accounting integrations that create invoices, sync payments, manage customers/vendors, pull finan