# How to Integrate Retool with Zoho Books

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

## TL;DR

Connect Retool to Zoho Books by creating a REST API Resource using Zoho's OAuth 2.0 self-client flow for server-to-server access. Configure the base URL with your Zoho region and organization ID, then build queries to manage invoices, expenses, and banking transactions. The integration enables an SMB financial dashboard that combines Zoho Books data with your CRM data already in Retool.

## Build a Zoho Books Financial Dashboard in Retool

Zoho Books is the accounting backbone for many SMBs using the Zoho ecosystem — it connects naturally with Zoho CRM, Zoho People, and other Zoho apps. When your business already uses Zoho CRM in Retool, adding Zoho Books enables a powerful combination: a customer dashboard that shows both CRM deals and accounting invoices, payment history, and outstanding balances for each customer.

Zoho Books exposes a comprehensive REST API v3 covering all accounting functions: invoices, bills, expenses, contacts, items, bank accounts, and financial reports. Authentication uses Zoho's OAuth 2.0 system, which powers authentication for all Zoho products. The recommended approach for server-to-server integrations (like Retool) is the self-client flow, which generates a refresh token without requiring a web browser redirect flow each time.

A key architectural detail with Zoho Books is that every API endpoint requires your organization ID as a query parameter. If you have multiple Zoho Books organizations (common for businesses with subsidiaries), you'll need to pass the correct organization ID for each query. Retool handles this well — store the organization ID in a Configuration Variable and reference it in all query parameters.

## Before you start

- A Zoho Books account with at least one organization set up
- Your Zoho Books organization ID (found in Zoho Books → Settings → Org Profile → Organization ID)
- Access to the Zoho API Console at https://api-console.zoho.com to create a self-client application
- Knowledge of your Zoho account's region: .com, .com.au, .eu, .in, or .jp (affects both OAuth and API URLs)
- A Retool account with permission to create Resources

## Step-by-step guide

### 1. Create a Zoho self-client app and generate a refresh token

Navigate to https://api-console.zoho.com and sign in with your Zoho account. This is Zoho's centralized API management console for all Zoho products.

Click Add Client and select Self Client. Self Client is the OAuth flow designed for server-to-server integrations where there's no user interaction — perfect for Retool. Fill in:
- Client Name: Retool Integration
- Homepage URL: https://retool.com (or your Retool instance URL)
- Authorized redirect URIs: https://retool.com (required field, but unused for self-client)

After creating, you receive a Client ID and Client Secret. Copy both.

Next, generate a code for token exchange. In the API Console, click your newly created self-client application. Go to the Generate Code tab. In the Scope field, enter the Zoho Books scopes you need:
- ZohoBooks.fullaccess.all (full access — use for development)
- ZohoBooks.invoices.READ,ZohoBooks.invoices.CREATE (specific scopes for production)

Set Time Duration to 10 minutes (this is how long the code is valid for the next step). Click Generate Code and copy the one-time code immediately.

Now exchange the code for a refresh token. This is a one-time operation. Use the Zoho token URL for your region:
- Global: https://accounts.zoho.com/oauth/v2/token
- EU: https://accounts.zoho.eu/oauth/v2/token
- India: https://accounts.zoho.in/oauth/v2/token

POST to the token URL with: grant_type=authorization_code, client_id=YOUR_CLIENT_ID, client_secret=YOUR_CLIENT_SECRET, redirect_uri=https://retool.com, code=YOUR_ONE_TIME_CODE. The response includes a refresh_token — copy and save this securely. This refresh token is long-lived and used to generate access tokens.

```
// Token exchange request (do this once using Postman or curl)
// POST https://accounts.zoho.com/oauth/v2/token
// (Use your regional URL if not global)
POST /oauth/v2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&redirect_uri=https://retool.com
&code=YOUR_ONE_TIME_CODE

// Response includes:
// {
//   "access_token": "1000.xxx...",
//   "refresh_token": "1000.xxx...",  // Save this!
//   "token_type": "Bearer",
//   "expires_in": 3600
// }
```

**Expected result:** You have a Zoho OAuth 2.0 refresh token (and optionally the initial access token) stored securely, ready to configure in Retool.

### 2. Set up token refresh in Retool Configuration

Zoho Books access tokens expire after 1 hour. To keep your Retool integration working long-term, configure automatic token refresh. There are two approaches:

Option A — Retool Custom Auth (recommended): Configure the Zoho REST API Resource to use Custom Auth. Set up an auth step that POSTs to Zoho's token endpoint with your refresh token to get a new access token. Retool will automatically run this when a query returns 401.

Add these Configuration Variables (Settings → Configuration Variables, all marked as secrets):
- ZOHO_CLIENT_ID
- ZOHO_CLIENT_SECRET
- ZOHO_REFRESH_TOKEN
- ZOHO_ORG_ID

For the Custom Auth configuration in the resource:
Step 1 (Request type): POST to https://accounts.zoho.com/oauth/v2/token with body: grant_type=refresh_token&refresh_token={{ retoolContext.configVars.ZOHO_REFRESH_TOKEN }}&client_id={{ retoolContext.configVars.ZOHO_CLIENT_ID }}&client_secret={{ retoolContext.configVars.ZOHO_CLIENT_SECRET }}
Step 2: Store response.access_token as the AUTH_TOKEN variable
Step 3: Set Authorization header to Bearer {{ auth.AUTH_TOKEN }}

Option B — Manual refresh: Periodically run the refresh token request manually, update the ZOHO_ACCESS_TOKEN configuration variable with the new token, and set it directly as a Bearer header. This requires manual maintenance every hour, which is impractical for production.

Use Option A for any production Retool tool.

```
// Refresh token request (for manual approach or reference)
// POST https://accounts.zoho.com/oauth/v2/token
// Content-Type: application/x-www-form-urlencoded
//
grant_type=refresh_token
&refresh_token={{ retoolContext.configVars.ZOHO_REFRESH_TOKEN }}
&client_id={{ retoolContext.configVars.ZOHO_CLIENT_ID }}
&client_secret={{ retoolContext.configVars.ZOHO_CLIENT_SECRET }}

// Response:
// {
//   "access_token": "1000.new_token...",
//   "token_type": "Bearer",
//   "expires_in": 3600
// }
```

**Expected result:** Token refresh is configured. The Retool resource will automatically obtain a new access token when the current one expires, without manual intervention.

### 3. Create the Zoho Books REST API Resource

In Retool, navigate to Resources → Add Resource → REST API.

Name: Zoho Books API

Base URL: https://www.zohoapis.com/books/v3
(For EU region: https://www.zohoapis.eu/books/v3)
(For India region: https://www.zohoapis.in/books/v3)

Authentication: Configure Custom Auth as described in Step 2, or if using a manually refreshed token: select Bearer Token and use {{ retoolContext.configVars.ZOHO_ACCESS_TOKEN }}.

Headers: Add Content-Type: application/json

IMPORTANT: Every Zoho Books API request requires an organization_id query parameter. You can add this as a default URL parameter in the resource: Key: organization_id, Value: {{ retoolContext.configVars.ZOHO_ORG_ID }}.

This means every query you write automatically includes the organization ID without needing to add it manually to each query.

Click Create Resource. Test with GET /invoices?page=1&per_page=5 — this should return recent invoices in JSON format.

**Expected result:** The Zoho Books REST API Resource is created and a test query to /invoices returns invoice data in JSON format.

### 4. Query invoices and build the invoice management view

Create a getInvoices query. Set method to GET and path to /invoices. Add query parameters:
- status: unpaid (filter — also: 'paid', 'overdue', 'draft', 'void', 'all')
- sort_column: created_time
- sort_order: D (descending)
- page: {{ pagination.offset / 25 + 1 }}
- per_page: 25
- date_start: {{ datePicker_start.value }}
- date_end: {{ datePicker_end.value }}
- customer_name: {{ textInput_search.value }}

The response includes an invoices array. Each invoice has: invoice_id, invoice_number, customer_name, customer_id, date, due_date, status, total, balance, and currency_code.

To get the full invoice details including line items, use GET /invoices/{{ table1.selectedRow.data.invoice_id }}. This returns the complete invoice with line_items (each with item_id, description, quantity, rate, tax_name, item_total).

Create a transformer to add a days_overdue calculation for each overdue invoice and format currency values:

```
// Transformer for Zoho Books invoices
const invoices = data.invoices || [];
const now = new Date();

return invoices.map(inv => {
  const dueDate = inv.due_date ? new Date(inv.due_date) : null;
  const daysOverdue = dueDate && inv.status === 'overdue'
    ? Math.floor((now - dueDate) / 86400000) : 0;

  return {
    id: inv.invoice_id,
    invoice_number: inv.invoice_number,
    customer: inv.customer_name,
    customer_id: inv.customer_id,
    date: inv.date,
    due_date: inv.due_date || 'N/A',
    status: inv.status,
    total: `${inv.currency_code} ${parseFloat(inv.total).toFixed(2)}`,
    balance: `${inv.currency_code} ${parseFloat(inv.balance).toFixed(2)}`,
    days_overdue: daysOverdue,
    is_overdue: daysOverdue > 0
  };
});
```

**Expected result:** The getInvoices query returns invoices with overdue calculations. The Table shows color-coded overdue invoices with balance and days past due.

### 5. Fetch contacts and expenses, build the combined view

Create a getContacts query to support customer lookup and the CRM cross-reference feature. GET /contacts with parameters:
- contact_type: customer (use 'vendor' for supplier contacts)
- search_text: {{ textInput_customerSearch.value }}
- sort_column: created_time
- per_page: 50

For expenses, create a getExpenses query: GET /expenses with:
- status: unbilled (also: 'invoiced', 'reimbursed', 'non-reimbursable', 'all')
- date_start: {{ datePicker_start.value }}
- date_end: {{ datePicker_end.value }}
- account_name: {{ select_expenseAccount.value }}

For the Zoho ecosystem cross-integration, if you also have Zoho CRM connected as a separate Retool resource, you can run a JavaScript query that fetches customer data from both resources and merges them by email address or customer name. This produces the combined CRM + accounting view described in the use cases.

For bank accounts and reconciliation data, use GET /bankaccounts to list bank accounts and GET /banktransactions?account_id={id} for transactions.

For simple financial reporting, the /reports endpoint provides profit & loss (GET /reports/profitandloss), balance sheet (GET /reports/balancesheet), and account transactions.

For teams combining Zoho Books with Zoho CRM in Retool, RapidDev's team can help build the cross-resource data model that links CRM contacts to accounting records.

```
// Transformer for expenses with category grouping
const expenses = data.expenses || [];

// Group by category for summary
const categoryTotals = {};
expenses.forEach(exp => {
  const cat = exp.account_name || 'Uncategorized';
  categoryTotals[cat] = (categoryTotals[cat] || 0) + parseFloat(exp.total || 0);
});

const expenseRows = expenses.map(exp => ({
  id: exp.expense_id,
  date: exp.date,
  merchant: exp.merchant_name || exp.reference_number || 'N/A',
  category: exp.account_name || 'Uncategorized',
  amount: parseFloat(exp.total).toFixed(2),
  currency: exp.currency_code,
  status: exp.status,
  paid_through: exp.paid_through_account_name || '',
  description: exp.description || ''
}));

return expenseRows;
```

**Expected result:** The Retool app shows contacts, invoices, and expenses. A combined CRM + accounting customer view is available when both Zoho CRM and Zoho Books resources are connected.

### 6. Build invoice actions and financial summary

Add action capabilities to your invoice management panel. Create these action queries:

Send payment reminder: POST /invoices/{{ table1.selectedRow.data.id }}/actions/email with body: {"to_mail_ids": ["{{ table1.selectedRow.data.customer_email }}"], "subject": "Payment Reminder", "body": "{{ textArea_reminderBody.value }}"}.

Create invoice: POST /invoices with a JSON body containing customer_id, date, due_date, and line_items array.

Mark invoice paid: POST /invoices/{{ table1.selectedRow.data.id }}/payments with payment amount and date.

Void invoice: POST /invoices/{{ table1.selectedRow.data.id }}/actions/void.

For the financial summary section, create Stat components showing:
- Total Overdue: Sum of balance for all overdue invoices
- Due This Week: Sum of balance for invoices due in next 7 days  
- Monthly Revenue: Sum of total for invoices paid this month
- Unpaid Invoice Count: Count of status=unpaid invoices

Calculate these in a JavaScript query that runs after getInvoices completes, using Retool's event handler chain (getInvoices → On Success → trigger calculateSummary).

```
// Financial summary calculator (JavaScript query)
const invoices = getInvoices.data || [];
const now = new Date();
const oneWeekFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);

const totalOverdue = invoices
  .filter(i => i.is_overdue)
  .reduce((sum, i) => sum + parseFloat(i.balance || 0), 0);

const dueThisWeek = invoices
  .filter(i => {
    const due = new Date(i.due_date);
    return due >= now && due <= oneWeekFromNow && i.status === 'unpaid';
  })
  .reduce((sum, i) => sum + parseFloat(i.balance || 0), 0);

const overdueCount = invoices.filter(i => i.is_overdue).length;
const unpaidCount = invoices.filter(i => i.status === 'unpaid').length;

return {
  total_overdue: totalOverdue.toFixed(2),
  due_this_week: dueThisWeek.toFixed(2),
  overdue_count: overdueCount,
  unpaid_count: unpaidCount
};
```

**Expected result:** The invoice management panel supports sending payment reminders, viewing full invoice details, and shows financial summary stats for overdue and upcoming payments.

## Best practices

- Use Zoho's self-client OAuth flow for server-to-server Retool integrations — it generates a long-lived refresh token without requiring browser-based user interaction.
- Configure Custom Auth in the Retool resource for automatic token refresh — Zoho access tokens expire after 1 hour, making manual refresh impractical.
- Store all OAuth credentials (client ID, client secret, refresh token) and the organization ID in Retool Configuration Variables marked as secrets.
- Add organization_id as a default URL parameter in the REST API Resource so all queries automatically include it without manual addition.
- Use region-specific API and OAuth URLs matching your Zoho account region — using the wrong regional URL causes authentication or data access failures.
- Request only the OAuth scopes you need (e.g., ZohoBooks.invoices.READ,ZohoBooks.invoices.CREATE) rather than ZohoBooks.fullaccess.all — this limits risk if credentials are compromised.
- For multi-organization Zoho Books setups, store each organization ID and create a dropdown in Retool to switch between them — parameterize the organization_id query parameter instead of hardcoding it.
- Combine Zoho Books with Zoho CRM in Retool by creating two separate REST API Resources — both use the same OAuth credentials (same self-client app and refresh token), just different base URLs.

## Use cases

### Build a combined CRM and accounting dashboard

Create a Retool dashboard that shows customer data from both Zoho CRM and Zoho Books side by side. When a sales manager selects a customer, see their CRM deal stage alongside their invoice history, total billed amount, outstanding balance, and payment track record — all in one view without switching between Zoho apps.

Prompt example:

```
Build a Retool customer 360 dashboard. Left panel: CRM contacts from Zoho CRM with name, company, deal stage, and deal value. Right panel: When a customer is selected, show their Zoho Books invoice history with invoice number, date, amount, status (paid/unpaid/overdue), and a total outstanding balance stat component.
```

### Build an invoice management and collections panel

Build a Retool collections dashboard showing all overdue invoices in Zoho Books. Sorted by days overdue and amount. Finance teams can view customer contact info, call status, and mark invoices as being followed up — without logging into Zoho Books' slower native interface.

Prompt example:

```
Build a Retool collections panel showing all overdue Zoho Books invoices. Show customer name, invoice number, invoice date, due date, days overdue, and balance due. Sort by days overdue descending. Add a Send Reminder button that triggers a Zoho Books payment reminder email. Include a total overdue amount stat at the top.
```

### Build an expense tracking and approval workflow

Create a Retool expense management panel where finance teams view all submitted expenses, approve or reject them, and categorize them. This adds a workflow approval layer on top of Zoho Books' expense module that isn't available in the native interface.

Prompt example:

```
Build a Retool expense approval panel showing expenses submitted in Zoho Books this month. Show expense date, merchant, amount, category, and submitted by. Add Approve and Reject buttons with a notes field. On approval, update the expense status via the Zoho Books API and record the approver in a notes field.
```

## Troubleshooting

### 401 Unauthorized with 'Invalid OAuthtoken' error

Cause: The Zoho access token has expired (tokens last 1 hour). If you're not using Custom Auth with automatic refresh, the token needs to be manually renewed.

Solution: Implement Custom Auth in the Retool resource for automatic token refresh (as described in Step 2). For immediate fix, run the refresh token request manually and update the ZOHO_ACCESS_TOKEN configuration variable with the new token.

### All requests return 'Organization not found' or empty results

Cause: The organization_id query parameter is missing or incorrect. Every Zoho Books API request requires the organization ID.

Solution: Verify your organization ID in Zoho Books → Settings → Org Profile. Ensure the organization_id is being added to every request, either as a default URL parameter in the resource configuration or in each query's URL parameters. Double-check there are no spaces or extra characters in the ID.

### Token refresh returns 'Invalid Code' during initial setup

Cause: The one-time authorization code expired before it was used. The code is only valid for the duration specified when generating it (default 10 minutes).

Solution: Go back to the Zoho API Console, generate a new authorization code, and immediately use it in the token exchange request. Prepare the token exchange request (in Postman or curl) before clicking Generate Code so you can use it within the time limit.

### API returns data but with incorrect currency or region-specific formatting

Cause: Using the wrong regional API URL. Zoho has region-specific API endpoints that serve data for accounts in that region.

Solution: Check your Zoho account region. If your organization is in India, use https://www.zohoapis.in/books/v3. For EU organizations, use https://www.zohoapis.eu/books/v3. For Australia, use https://www.zohoapis.com.au/books/v3. The global endpoint (.com) serves US accounts.

## Frequently asked questions

### Does Retool have a native Zoho Books connector?

No. Retool connects to Zoho Books via a REST API Resource. Retool does have a native Zoho CRM connector, which uses different authentication. For Zoho Books, you configure a separate REST API Resource with Zoho's OAuth 2.0 credentials and the Zoho Books API base URL.

### Can I use the same OAuth credentials for both Zoho Books and Zoho CRM in Retool?

Yes, if your self-client application has scopes for both products. When creating the authorization code, specify both Zoho Books and Zoho CRM scopes (e.g., ZohoBooks.fullaccess.all,ZohoCRM.modules.ALL). The resulting refresh token works for both products. In Retool, create two separate REST API Resources — one pointing to books.zohoapis.com and one to www.zohoapis.com/crm — both using the same OAuth credentials.

### How do I find my Zoho Books organization ID?

In Zoho Books, click the Settings gear icon (top-right) → Organization Profile. Your Organization ID is shown on this page (a long numeric ID). Alternatively, call GET /organizations on the Zoho Books API — it returns all organizations associated with your Zoho account with their IDs.

### Does Zoho Books have a sandbox environment for testing?

Yes. Zoho Books provides a sandbox environment at https://sandbox.zohoapis.com/books/v3. Sandbox credentials are separate from production — create a sandbox organization in the Zoho Books sandbox environment and generate separate OAuth credentials. Always test API integrations in the sandbox before pointing your Retool resource to the production API.

### How do I handle multiple Zoho Books organizations in Retool?

If you manage multiple Zoho Books organizations (e.g., different business entities), create a Select component in Retool that lets users choose the organization. Store all organization IDs in a configuration list or database table. Reference the selected organization ID in all query parameters: organization_id={{ select_org.value }}. The same OAuth credentials work across all organizations in your Zoho account.

---

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