Tally API for ERP, CRM and SaaS developers
Expose simple JSON endpoints from your software. Our Tally connector pulls ledgers, stock items and vouchers into Tally, then posts the Tally ID back to you for every record. No TDL, no XML, no port 9000.
{"Data": [
{
"Action": "Create",
"TallyId": 0,
"ERPId": "133",
"Name": "Sharma Traders",
"Parent": "Sundry Debtors"
}
]}
{
"TallyId": 12345,
"ERPId": "133",
"Status": "Yes"
}
Your software stays the source of truth. The connector pulls on schedule, writes to Tally and tells you exactly what it saved. You never open a connection into the customer's network.
One endpoint per master type plus one for vouchers. Return pending records as JSON under a Data array.
The Tally connector calls each endpoint with your bearer token, in real time or on a scheduled batch.
Each record carries an action. Create adds it, Update replaces it by Tally ID, Delete removes it.
The result of every record is POSTed to /sync-ack/{master}. Store the Tally ID for future updates and deletes.
Every endpoint is on your server. Paths below are relative to the base URL you register with us. Field-level schemas for each master are in the full API docs.
| Method | Path | Purpose |
|---|---|---|
| Accounting masters | ||
| GET | /masters/ledger-group | Account groups under Tally's chart of accounts |
| GET | /masters/ledger | Customers, suppliers, bank, tax and expense ledgers |
| GET | /masters/cost-category | Cost categories |
| GET | /masters/cost-centre | Cost centres for branch / project tracking |
| GET | /masters/voucher-type | Custom voucher types and numbering |
| Inventory masters | ||
| GET | /masters/unit | Units of measure |
| GET | /masters/stock-group | Stock groups |
| GET | /masters/stock-category | Stock categories |
| GET | /masters/godown | Godowns / warehouse locations |
| GET | /masters/stock-item | Stock items with unit, group and category |
| Transactions | ||
| GET | /vouchers | Sales, purchase, receipt, payment, journal and other vouchers |
| Acknowledgment (connector calls you) | ||
| POST | /sync-ack/{master} | Result per record: TallyId, ERPId, Status |
Every call carries a static bearer token with no expiry. Reject requests without it.
Authorization: Bearer YOUR_TOKEN
Tally needs a parent before its child, so send masters before the vouchers that use them.
Set Action on each record. The Tally ID you stored from the acknowledgment is how the connector finds the record again.
Adds the master or voucher if it doesn't exist. Send TallyId: 0.
{"Action": "Create",
"TallyId": 0,
"ERPId": "133", ...}
Finds the record by Tally ID, clears its child collections and re-inserts your payload. If the ID isn't found, it is created instead.
{"Action": "Update",
"TallyId": 12345,
"ERPId": "133", ...}
Permanently removes the record identified by its Tally ID. There is no undo inside Tally.
{"Action": "Delete",
"TallyId": 12345,
"ERPId": "133", ...}
The validator and the connector enforce all of these. Get them right once and every master type works the same way.
Every response is an object with a Data key holding an array. Tally can't read any other shape.
Valid, parseable JSON with status 200. An empty sync is {"Data": []}, not a 404.
Eight digits, no separators. Leave an empty date as an empty string.
✓ "20250401"Never omit a key. Optional fields can be null or ""; the key itself must be there.
Mandatory fields can't be null, undefined or empty. The validator names the exact field that failed.
✓ "Name": "Sharma Traders"Use 0 for new records and the stored numeric ID for updates and deletes. Never send a string.
✓ 0, 12345A GET that returns pending ledgers and a POST that stores the acknowledgment. The same pattern repeats for every master and for vouchers.
// GET /masters/ledger - return ledgers waiting to sync [HttpGet, Route("masters/ledger")] public IHttpActionResult GetLedgers() { if (!IsValidToken(Request)) return Unauthorized(); var data = db.Ledgers.Where(l => l.PendingSync).ToList() .Select(l => new { Action = l.TallyId > 0 ? "Update" : "Create", TallyId = l.TallyId, // 0 for new records ERPId = l.Id.ToString(), Name = l.Name, Parent = l.GroupName // ...remaining ledger fields from the API docs }); return Ok(new { Data = data }); // always wrap in Data } // POST /sync-ack/ledger - store what Tally saved [HttpPost, Route("sync-ack/{master}")] public IHttpActionResult SyncAck(string master, SyncAck ack) { if (!IsValidToken(Request)) return Unauthorized(); if (ack.Status == "Yes") db.MarkSynced(master, ack.ERPId, ack.TallyId); // keep TallyId! else db.MarkFailed(master, ack.ERPId); return Ok(); }
// GET /masters/ledger - return ledgers waiting to sync app.get('/masters/ledger', requireToken, async (req, res) => { const rows = await db.ledgers.findPendingSync(); res.status(200).json({ Data: rows.map(l => ({ Action: l.tallyId ? 'Update' : 'Create', TallyId: l.tallyId || 0, // 0 for new records ERPId: String(l.id), Name: l.name, Parent: l.groupName // ...remaining ledger fields from the API docs })) }); }); // POST /sync-ack/:master - store what Tally saved app.post('/sync-ack/:master', requireToken, async (req, res) => { const { TallyId, ERPId, Status } = req.body; if (Status === 'Yes') await db.markSynced(req.params.master, ERPId, TallyId); else await db.markFailed(req.params.master, ERPId); res.sendStatus(200); });
# GET /masters/ledger - return ledgers waiting to sync @app.get("/masters/ledger") @require_token def get_ledgers(): rows = db.ledgers_pending_sync() return jsonify({"Data": [{ "Action": "Update" if r.tally_id else "Create", "TallyId": r.tally_id or 0, # 0 for new records "ERPId": str(r.id), "Name": r.name, "Parent": r.group_name, # ...remaining ledger fields from the API docs } for r in rows]}), 200 # POST /sync-ack/<master> - store what Tally saved @app.post("/sync-ack/<master>") @require_token def sync_ack(master): ack = request.get_json() if ack["Status"] == "Yes": db.mark_synced(master, ack["ERPId"], ack["TallyId"]) else: db.mark_failed(master, ack["ERPId"]) return "", 200
# 1. Check your GET endpoint returns {"Data": [...]} curl -i https://your-erp.com/api/masters/ledger \ -H "Authorization: Bearer YOUR_TOKEN" # 2. Simulate the acknowledgment the connector will send curl -i -X POST https://your-erp.com/api/sync-ack/ledger \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"TallyId": 12345, "ERPId": "133", "Status": "Yes"}'
Samples are trimmed to show the pattern. Use the full field list for each master from the API docs.
Paste your endpoint URL and token into the API testing portal. It calls your endpoint, counts the records and flags every error with the record and field that caused it. Fix, re-run, go live.
Data keyMasters first, then the vouchers that use them. One pattern covers every type.
Selling on Amazon, Flipkart, Shopify or collecting through Razorpay and PayU? Use our ready eCommerce and payment gateway integrations instead. No code needed.
You can talk to Tally's XML interface directly. Most teams find out why they shouldn't after the first production bug.
| Building on Tally XML yourself | Using our Tally API | |
|---|---|---|
| Data format | Tally-specific XML envelopes and TDL | Plain JSON under a Data key |
| Updates and deletes | Write your own matching and alter logic | Handled by Tally ID |
| Knowing what saved | Parse Tally's import responses yourself | Per-record acknowledgment POSTed to you |
| Network setup | Reach each customer's Tally on port 9000 | Connector calls out to your API over HTTPS |
| Testing | Trial and error on a live company | Online validator before go-live |
| Tally expertise | Hire or train Tally specialists | 10+ years of Tally partner support included |
| Time to first sync | Weeks to months | Typically 3–7 business days |
Offer a Tally connector to every customer without hiring Tally specialists or shipping TDL files.
Billing, POS, school, hospital or distribution software that has to post into a client's books.
Connect your company's own system to the Tally your accounts team already runs.
Automate posting for clients whose operations run on a separate system, without manual re-entry.
ERP and CRM integrations are quoted on the masters, voucher types and number of Tally companies involved. Prices are annual. GST at 18% applies extra.
Custom quote
For one company connecting its own ERP, CRM or app to Tally.
Custom quote
For software sold to many businesses that each run Tally.
All editions of TallyPrime (Silver and Gold) and Tally ERP 9.
No. You only expose JSON endpoints. The connector handles everything inside Tally.
A static bearer token with no expiry, sent in the header as Authorization: Bearer YOUR_TOKEN. Serve your endpoints over HTTPS.
Send it with "Action": "Update" and the Tally ID you stored from the acknowledgment. The connector clears the record's child collections and re-inserts your payload. If that ID isn't found, the record is created instead.
The acknowledgment comes back with "Status": "No" for that record. Keep it pending in your system, fix the data and it will be picked up in the next sync.
No. Keep every key in the payload. Optional fields can be null or an empty string.
Parents before children: ledger groups before ledgers, units and stock groups before stock items, and all masters before the vouchers that use them.
It can run in real time or as a scheduled batch, depending on your volume and how fresh your accounts team needs the books.
Yes. Two-way sync is available on the software vendor / multi-client plan.
Most custom ERP integrations go live in 3 to 7 business days, depending on how many masters and voucher types are involved.
Tell us what you're connecting. We'll share your token, a test Tally company and a walkthrough of the endpoints for your use case.
Prefer to talk? Call or WhatsApp 93584 44502
Share your details and our team will contact you within 2 business hours.
Get API access