Two ERPs that answer 200 when they fail
Merchants keep their books in Tally or BUSY on a desktop. The platform needs items, customers, stock, prices and balances to flow in, and orders, delivery notes and invoices to flow out. Both ERPs report failure as success.
- Tally add-on
- Sole author6,000+ lines of TDL
- releases
- Versionedwith in-app auto-update
- BUSY plugin
- Sole authorreverse-engineered
- stack
- TDLNode.js, BullMQ, XML
TallyHTTP/1.1 200 OK <RESPONSE> <CREATED>0</CREATED> <ERRORS>1</ERRORS> <LINEERROR>...</LINEERROR> </RESPONSE>
BUSYHTTP/1.1 200 OK Result: F Description: ... <z:row Col1="..." Col2="..." />
the claim
Tally answers HTTP 200 for every request and reports the outcome inside the XML, as STATUS, LINEERROR, ERRORS or EXCEPTIONS. An import result can even report STATUS 1 while hiding failures. BUSY exposes one GET endpoint driven by numeric service codes in headers, returns ADO rowsets with columns as attributes, and also answers 200 whatever happens. The BUSY code that existed before me had never actually run.
what I built
- A Tally add-on in TDL, over 6,000 lines across 19 modules: two-way sync, extra fields on the stock item screen, a Send Invoice button on the voucher form, an auth and health check, and an in-app updater that downloads and hot-reloads a new build.
- The full BUSY client (service codes 1 to 9): catalogue, stock, price and outstanding sync, orders as sale orders, and shipments as delivery challans.
- Response handling that reads the body and headers for the real outcome, never the status code, with credentials redacted from every log line.
three more lies
Batch Post. Tally's documented Start Batch Post is a no-op for an HTTP JSON collection, so whole catalogues went out in one POST and the API rejected them with a 413. I wrote a two-pass pager in TDL that posts 500 rows at a time and stamps the returned ids back onto each master.
The timeout. A 26 MB Day Book export blocked Node's event loop for minutes, long enough that the request's own abort timer never fired. A size guard now runs before parsing, and big exports fall back to reading ids first, then exporting in 500-master windows. A 28.9 MB export that used to fail now completes in bounded batches.
The migration. Moving clients from the TDL add-on to the desktop connector would have re-imported every catalogue as duplicates, because the two generations mint different sync ids from the same Tally master. Reconciliation now lets the legacy id win wherever one exists. No client got a duplicate catalogue.
what I built next
Sync alone meant sellers still kept two sets of books. So I rebuilt purchases and sales on a books layer shaped like Tally's own model: groups, ledgers and vouchers, with stock lines and bill-wise allocations.
Where a store runs Tally or BUSY, the ERP owns the invoice and the platform keeps a replica, synced both ways. Where a store runs no ERP, the platform's own books do the job. When to invoice is set per store, because one client invoices on acceptance and another on dispatch.
what changed
Item and customer sync work on companies of any size, in both directions. The longer story, with the XML, is in Building a two-way Tally ERP sync that survives retries.