Zoho API Integration
Quick Reference
| API |
Base URL |
| Zoho Inventory |
https://www.zohoapis.com/inventory/v1 |
| Zoho Books |
https://www.zohoapis.com/books/v3 |
Organization ID: 748369814
Code Files
| File |
Purpose |
src/lib/zoho/client.ts |
OAuth client, token caching, zohoFetch |
src/lib/zoho/products.ts |
Products, stock extraction |
src/lib/zoho/price-lists.ts |
Price list constants and fetching |
src/lib/zoho/customers.ts |
Customer lookup by email |
src/lib/zoho/orders.ts |
Sales orders |
src/lib/zoho/invoices.ts |
Invoices |
src/lib/zoho/payments.ts |
Payments |
src/lib/zoho/credit-notes.ts |
Credit notes |
Using zohoFetch
import { zohoFetch } from '@/lib/zoho/client';
// GET request
const data = await zohoFetch('/inventory/v1/items', {
params: {
organization_id: process.env.ZOHO_ORGANIZATION_ID,
page: 1,
per_page: 100,
},
});
// Single item with locations
const item = await zohoFetch(`/inventory/v1/items/${itemId}`, {
params: { organization_id: process.env.ZOHO_ORGANIZATION_ID },
});
Token Caching (CRITICAL)
Memory Cache → Upstash Redis → Zoho OAuth Refresh
(50-min TTL) (rate limit: 10s guard)
If all prices show "Contact for price", check:
- Upstash env vars in Vercel
UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN
- Run:
curl https://www.tsh.sale/api/debug/token
Common Endpoints
Inventory API
GET /items # List products
GET /items/{id} # Single product (includes locations)
GET /categories # List categories
GET /pricebooks/{id} # Pricebook with items
Books API
GET /contacts # List customers
GET /salesorders # Sales orders
GET /invoices # Invoices
GET /customerpayments # Payments
GET /creditnotes # Credit notes
Caching with unstable_cache
import { unstable_cache } from 'next/cache';
const getCachedProducts = unstable_cache(
async () => await fetchProducts(),
['products'],
{ revalidate: 3600, tags: ['products'] }
);
Revalidate cache:
curl "https://www.tsh.sale/api/revalidate?tag=products&secret=tsh-revalidate-2024"
Error Handling Pattern
try {
const data = await zohoFetch('/inventory/v1/items', { ... });
} catch (error) {
if (error.message.includes('401')) {
// Token expired - auto-refreshes
} else if (error.message.includes('429')) {
// Rate limited - wait and retry
}
}
Creating New API Route
// src/app/api/zoho/[resource]/route.ts
import { NextResponse } from 'next/server';
import { zohoFetch } from '@/lib/zoho/client';
export async function GET(request: Request) {
const data = await zohoFetch('/inventory/v1/items', {
params: {
organization_id: process.env.ZOHO_ORGANIZATION_ID,
},
});
return NextResponse.json(data);
}
Rate Limits
| API |
Limit |
| OAuth Refresh |
~100/minute |
| Inventory API |
100/minute |
| Books API |
100/minute |
Debug Commands
# Check token
curl "https://www.tsh.sale/api/debug/token"
# Check prices
curl "https://www.tsh.sale/api/debug/prices"
# Check stock
curl "https://www.tsh.sale/api/debug/stock"
# Revalidate cache
curl "https://www.tsh.sale/api/revalidate?tag=all&secret=tsh-revalidate-2024"