Tally API for ERP, CRM and SaaS developers

Send JSON.
It lands in TallyPrime.

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.

  • 10 master types + vouchers
  • Create, Update, Delete
  • Per-record acknowledgments
  • Online endpoint validator
1 · Connector calls you: GET /masters/ledger
{"Data": [
  {
                    "Action":  "Create",
                    "TallyId": 0,
                    "ERPId":   "133",
                    "Name":    "Sharma Traders",
                    "Parent":  "Sundry Debtors"
  }
]}
↓ saved in TallyPrime ↓
2 · Connector confirms: POST /sync-ack/ledger
{
                    "TallyId": 12345,
                    "ERPId":   "133",
                    "Status":  "Yes"
}
10+ yearsTally Associate Partner
5,000+businesses on our integrations
1 Cr+entries imported into Tally
30+platforms connected
3–7 daystypical custom ERP go-live
How it works

One sync cycle, four steps

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.

1 Your ERP

Expose GET endpoints

One endpoint per master type plus one for vouchers. Return pending records as JSON under a Data array.

2 Connector → your ERP

Connector pulls

The Tally connector calls each endpoint with your bearer token, in real time or on a scheduled batch.

3 Connector → TallyPrime

Records are posted

Each record carries an action. Create adds it, Update replaces it by Tally ID, Delete removes it.

4 Connector → your ERP

You get an ACK

The result of every record is POSTed to /sync-ack/{master}. Store the Tally ID for future updates and deletes.

API reference

Endpoints you expose

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.

MethodPathPurpose
Accounting masters
GET/masters/ledger-groupAccount groups under Tally's chart of accounts
GET/masters/ledgerCustomers, suppliers, bank, tax and expense ledgers
GET/masters/cost-categoryCost categories
GET/masters/cost-centreCost centres for branch / project tracking
GET/masters/voucher-typeCustom voucher types and numbering
Inventory masters
GET/masters/unitUnits of measure
GET/masters/stock-groupStock groups
GET/masters/stock-categoryStock categories
GET/masters/godownGodowns / warehouse locations
GET/masters/stock-itemStock items with unit, group and category
Transactions
GET/vouchersSales, purchase, receipt, payment, journal and other vouchers
Acknowledgment (connector calls you)
POST/sync-ack/{master}Result per record: TallyId, ERPId, Status

Authentication

Every call carries a static bearer token with no expiry. Reject requests without it.

Authorization: Bearer YOUR_TOKEN

Recommended sync order

Tally needs a parent before its child, so send masters before the vouchers that use them.

  1. Ledger groups, then ledgers
  2. Cost categories, then cost centres
  3. Units, stock groups, stock categories
  4. Godowns, then stock items
  5. Voucher types
  6. Vouchers
Actions

Create, update and delete with one field

Set Action on each record. The Tally ID you stored from the acknowledgment is how the connector finds the record again.

Create New record

Adds the master or voucher if it doesn't exist. Send TallyId: 0.

{"Action": "Create",
 "TallyId": 0,
 "ERPId": "133", ...}

Update Replace by ID

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", ...}

Delete Remove for good

Permanently removes the record identified by its Tally ID. There is no undo inside Tally.

{"Action": "Delete",
 "TallyId": 12345,
 "ERPId": "133", ...}
Payload rules

Six rules that prevent 90% of sync errors

The validator and the connector enforce all of these. Get them right once and every master type works the same way.

Wrap everything in Data

Every response is an object with a Data key holding an array. Tally can't read any other shape.

✓ {"Data": [ ... ]}   ✗ [ ... ]

Return HTTP 200

Valid, parseable JSON with status 200. An empty sync is {"Data": []}, not a 404.

✓ 200 {"Data": []}

Dates as YYYYMMDD

Eight digits, no separators. Leave an empty date as an empty string.

✓ "20250401"   ✗ "01-04-2025"

Keep every key

Never omit a key. Optional fields can be null or ""; the key itself must be there.

✓ "Narration": ""   ✗ (key missing)

Mandatory means filled

Mandatory fields can't be null, undefined or empty. The validator names the exact field that failed.

✓ "Name": "Sharma Traders"   ✗ "Name": ""

TallyId: 0 or numeric

Use 0 for new records and the stored numeric ID for updates and deletes. Never send a string.

✓ 0, 12345   ✗ "12345"
Code samples

Two endpoints and you're syncing

A 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.

Testing portal

Test your endpoints before Tally ever sees them

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.

  • Your API returns HTTP 200 with valid JSON
  • Records sit in an array under the Data key
  • Dates use the YYYYMMDD format
  • Mandatory fields are present and not empty
  • Tally ID is 0 for new records and numeric for updates
GET /masters/ledger200 OK · 3 records
  • HTTP status is 200
  • Response is valid JSON
  • "Data" key found, value is an array
  • Record 1 · all checks passed
  • Record 2 · "Name" is mandatory and empty
  • Record 3 · "TallyId" must be numeric
Coverage

Everything your books need

Masters first, then the vouchers that use them. One pattern covers every type.

Masters

  • Ledgers and ledger groups
  • Cost centres and cost categories
  • Voucher types
  • Godowns
  • Stock items, groups and categories
  • Units of measure

Transactions

  • Sales and purchase invoices
  • Receipts and payments
  • Journals, contra and other vouchers
  • Inventory and cost-centre allocations

Systems we've already connected to Tally

Zoho Books / CRM Odoo SAP Salesforce QuickBooks Busy Marg Custom ERPs

Not building an ERP?

Selling on Amazon, Flipkart, Shopify or collecting through Razorpay and PayU? Use our ready eCommerce and payment gateway integrations instead. No code needed.

Build vs buy

Skip months of Tally XML work

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 formatTally-specific XML envelopes and TDLPlain JSON under a Data key
Updates and deletesWrite your own matching and alter logicHandled by Tally ID
Knowing what savedParse Tally's import responses yourselfPer-record acknowledgment POSTed to you
Network setupReach each customer's Tally on port 9000Connector calls out to your API over HTTPS
TestingTrial and error on a live companyOnline validator before go-live
Tally expertiseHire or train Tally specialists10+ years of Tally partner support included
Time to first syncWeeks to monthsTypically 3–7 business days
Go-live checklist

From token to first sync

  1. Get your tokenRequest access and we set up a test Tally company for you.
  2. Build the endpointsStart with ledger groups and ledgers, then add the masters and vouchers you need.
  3. Pass the validatorRun every endpoint through the testing portal until all records pass.
  4. Handle the ACKStore each Tally ID against your record and mark failed records for retry.
  5. Sync the test companyCheck the entries in Tally with your accounts team before switching to live.
  6. Go livePoint the connector at the live company. Real-time or scheduled, your choice.
Who it's for

Built for teams whose customers use Tally

ERP and CRM vendors

Offer a Tally connector to every customer without hiring Tally specialists or shipping TDL files.

SaaS and app builders

Billing, POS, school, hospital or distribution software that has to post into a client's books.

In-house IT teams

Connect your company's own system to the Tally your accounts team already runs.

CAs and implementers

Automate posting for clients whose operations run on a separate system, without manual re-entry.

Pricing

Priced to your integration

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.

Software vendor / multi-client

Custom quote

For software sold to many businesses that each run Tally.

  • Multi-company and multi-client rollout
  • Custom field mapping and modules
  • Two-way sync back to your system
  • Priority implementation support
Talk to us
FAQ

Questions developers ask

Which Tally versions are supported?

All editions of TallyPrime (Silver and Gold) and Tally ERP 9.

Do I need to know TDL or Tally XML?

No. You only expose JSON endpoints. The connector handles everything inside Tally.

How does authentication work?

A static bearer token with no expiry, sent in the header as Authorization: Bearer YOUR_TOKEN. Serve your endpoints over HTTPS.

How do I update a record that is already in Tally?

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.

What happens when a record fails?

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.

Can optional fields be left out of the JSON?

No. Keep every key in the payload. Optional fields can be null or an empty string.

In what order should I send data?

Parents before children: ledger groups before ledgers, units and stock groups before stock items, and all masters before the vouchers that use them.

Is the sync real-time?

It can run in real time or as a scheduled batch, depending on your volume and how fresh your accounts team needs the books.

Can data flow from Tally back to my software?

Yes. Two-way sync is available on the software vendor / multi-client plan.

How long does integration take?

Most custom ERP integrations go live in 3 to 7 business days, depending on how many masters and voucher types are involved.

Get started

Get your API token

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.

  • Free technical demo with our Tally team
  • Help mapping your data to Tally masters
  • Support until your first live sync

Prefer to talk? Call or WhatsApp 93584 44502

Book a free demo

Share your details and our team will contact you within 2 business hours.

Get API access

Email support@tally-integration.in