From tally-erp
Read-only access to a running TallyPrime instance over its built-in XML/HTTP gateway. Lets Claude query ledgers, vouchers, stock items, day book, trial balance, P&L, balance sheet, and other standard reports without crafting raw XML.
How this skill is triggered — by the user, by Claude, or both
Slash command
/tally-erp:tally-erpThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Talk to a running **TallyPrime** instance via its XML/HTTP gateway and pull accounting data (ledgers, vouchers, stock, reports). Read-only.
bin/checksums.txttemplates/README.mdtemplates/collections/list_cost_centres.xmltemplates/collections/list_currencies.xmltemplates/collections/list_godowns.xmltemplates/collections/list_groups.xmltemplates/collections/list_ledgers.xmltemplates/collections/list_ledgers_by_group.xmltemplates/collections/list_stock_groups.xmltemplates/collections/list_stock_items.xmltemplates/collections/list_stock_items_by_group.xmltemplates/collections/list_units.xmltemplates/collections/list_voucher_types.xmltemplates/collections/list_vouchers_by_type.xmltemplates/collections/list_vouchers_dated.xmltemplates/collections/list_vouchers_with_items.xmltemplates/objects/group.xmltemplates/objects/ledger.xmltemplates/objects/stock_item.xmltemplates/objects/voucher.xmlTalk to a running TallyPrime instance via its XML/HTTP gateway and pull accounting data (ledgers, vouchers, stock, reports). Read-only.
F1 → Settings → Connectivity → Client/Server configuration → TallyPrime acts as Server, with port (default 9000).http://<host>:9000 from where Claude runs.Always invoke the bundled tally CLI; never curl/HTTP/XML by hand unless the user asks for raw XML or a feature isn't covered by typed subcommands (then use tally raw).
The binaries are in the bin/ directory relative to this skill's root. Pick the binary matching the OS where the agent is running:
| OS | Binary |
|---|---|
| Windows | bin/tally-windows-amd64.exe |
| macOS (Apple Silicon) | bin/tally-darwin-arm64 |
| macOS (Intel) | bin/tally-darwin-amd64 |
| Linux (x86_64) | bin/tally-linux-amd64 |
| Linux (ARM64) | bin/tally-linux-arm64 |
To detect the OS at runtime, run uname -s (returns Darwin, Linux, or use .exe on Windows) and uname -m (returns arm64 or x86_64). Determine the skill root from the path of this SKILL.md file and run the binary from there:
# SKILL_DIR is the directory containing this SKILL.md
SKILL_DIR="<path-to-this-skill>"
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
[ "$ARCH" = "x86_64" ] && ARCH="amd64"
[ "$OS" = "darwin" ] || [ "$OS" = "linux" ] && TALLY="$SKILL_DIR/bin/tally-${OS}-${ARCH}" || TALLY="$SKILL_DIR/bin/tally-windows-amd64.exe"
$TALLY ping
$TALLY report --company "ABC" --id "Day Book" --from 2026-04-01 --to 2026-04-30
TallyPrime itself only runs on Windows, but the CLI can run from any OS as long as the Tally gateway is network-reachable (use --host to point to the Windows machine).
(Throughout this document the binary is referred to as tally for brevity.)
All subcommands accept these global flags: --scheme (default http; set https for TLS), --host (default localhost), --port (default 9000), --company, --timeout (default 30s), --pretty.
tally pingConfirm Tally is reachable and responding. Does not require --company — pings the gateway with a List of Companies request, so it works even when no company is loaded.
tally ping
# stdout: tally: ok
# exit 0 on success; 2 if unreachable; 4 on timeout
tally companiesList loaded companies.
tally companies --pretty
tally objectExport a single object.
tally object \
--company "ABC Company Ltd" \
--subtype Ledger \
--id "Customer ABC" \
--fetch Name,Parent,ClosingBalance,MailingName,Address
Subtypes: Ledger, Group, StockItem, StockGroup, Voucher, CostCentre, Godown, Unit, Currency, VoucherType, Company.
tally collectionExport a list collection.
tally collection --company "ABC Company Ltd" --id "List of Ledgers"
Filtering flags (use these to avoid fetching all items from large collections):
| Flag | Purpose |
|---|---|
--parent | Filter to children of a group (TDL CHILD OF). E.g. --parent "Sundry Debtors" |
--fields | Comma-separated fields to return via TDL NATIVEMETHOD. E.g. --fields Name,Parent,ClosingBalance |
--filter | Raw TDL filter expression. E.g. --filter "$ClosingBalance > 10000" |
--fetch | Comma-separated FETCHLIST fields (standard XML mechanism) |
# Only ledgers under Sundry Debtors, returning just Name and ClosingBalance
tally collection --company "ABC" --id "List of Ledgers" \
--parent "Sundry Debtors" --fields Name,Parent,ClosingBalance
# Stock items with a filter expression
tally collection --company "ABC" --id "List of Stock Items" \
--filter "$ClosingBalance > 0" --fields Name,Parent,ClosingBalance
Common collection IDs: List of Companies, List of Groups, List of Ledgers, List of Cost Categories, List of Cost Centres, List of Stock Groups, List of Stock Categories, List of Stock Items, List of Godowns, List of Voucher Types, List of Budgets.
⚠️ Avoid the built-in
List of Currencies,List of Units, andList of Voucherscollection IDs — they crash TallyPrime when exported as XML. Use the bundled templates instead, which substitute custom TDL collections (TYPE=Currency,TYPE=Unit,TYPE=Voucher):tally template --name collections/list_currencies --company "ABC" tally template --name collections/list_units --company "ABC" tally template --name collections/list_vouchers_dated --company "ABC" --from 2026-04-01 --to 2026-04-30
tally reportExport a standard report.
tally report --company "ABC" --id "Day Book" --from 2026-04-01 --to 2026-04-30
tally report --company "ABC" --id "Ledger" --ledger "Customer ABC" --from 2026-04-01 --to 2026-04-30
tally report --company "ABC" --id "Group Outstandings" --group "Sundry Debtors"
# Filter Day Book to only Sales vouchers
tally report --company "ABC" --id "Day Book" --from 2026-04-01 --to 2026-04-30 --voucher-type Sales
# Arbitrary TDL filter on a report
tally report --company "ABC" --id "Day Book" --from 2026-04-01 --to 2026-04-30 --filter "$Amount > 50000"
Filtering flags:
| Flag | Purpose |
|---|---|
--voucher-type | Filter report by voucher type name (e.g. Sales, Purchase, Payment) |
--filter | Raw TDL filter expression |
--chunk | Date-based chunking: daily, weekly, or monthly. Splits the date range into sub-requests and merges results. Use for large companies where a single full-year request may timeout. |
Common report IDs and required variables:
| Report ID | Required |
|---|---|
Day Book | --from, --to |
Trial Balance | --from, --to |
Profit and Loss | --from, --to |
Balance Sheet | --from, --to |
Ledger | --ledger, --from, --to |
Ledger Outstandings | --ledger |
Group Outstandings | --group |
Bills Receivable | --from, --to |
Bills Payable | --from, --to |
Sales Register | --from, --to |
Purchase Register | --from, --to |
Cash Flow | --from, --to |
Funds Flow | --from, --to |
Stock Summary | --from, --to |
Godown Summary | --from, --to |
Movement Analysis | --from, --to |
List of Accounts | none |
For arbitrary additional STATICVARIABLES, use --var KEY=VALUE (repeatable). Use --explode to set EXPLODEFLAG=Yes.
tally templateLoad a bundled XML template, substitute {{KEY}} placeholders from CLI flags, and POST it to Tally. This is the recommended way to use the bundled templates/ envelopes — much nicer than sed + tally raw.
tally template --name reports/day_book \
--company "ABC Co" \
--from 2026-04-01 --to 2026-04-30
Substitution flags (in addition to globals):
| Flag | Placeholder |
|---|---|
--name (required) | (selects template; relative path under templates/, .xml optional) |
--templates-dir | (override; default auto: <exe>/../templates, then ./templates) |
--company (global) | {{COMPANY}} |
--from | {{FROMDATE}} (ISO YYYY-MM-DD -> Tally YYYYMMDD) |
--to | {{TODATE}} |
--ledger | {{LEDGER}} |
--group | {{GROUP}} |
--stockitem | {{STOCKITEM}} |
--vouchertype | {{VOUCHERTYPE}} |
--vouchernumber | {{VOUCHERNUMBER}} |
--chunk | Date chunking: daily, weekly, or monthly (splits date range, merges results) |
--var KEY=VALUE | {{KEY}} (repeatable, for any other placeholder) |
tally rawEscape hatch — submits a complete <ENVELOPE> from stdin or --file. Use only when typed subcommands don't cover the case (custom TDL, exotic variables).
cat my-request.xml | tally raw
tally raw --file my-request.xml
The templates/ directory contains ready-to-use envelope templates with {{COMPANY}}, {{FROMDATE}}, etc. placeholders that pair well with tally raw.
tally syncFetch data from Tally and write it to a local file-tree cache for fast offline queries. Uses monthly chunking automatically to handle large companies.
tally sync --type sales --from 2025-04-01 --to 2026-03-31 \
--company "ABC" --host localhost --port 9000
tally sync --type purchase --from 2025-04-01 --to 2026-03-31 \
--company "ABC" --scheme https --host tally.example.com --port 443
| Flag | Purpose |
|---|---|
--type (required) | sales or purchase |
--from, --to (required) | Date range to sync (YYYY-MM-DD) |
--cache-dir | Override cache directory (default .tally-cache/) |
| Global flags | --company, --host, --port, --scheme, --timeout |
Creates a partitioned file tree at .tally-cache/<company>/<type>/:
all.csv — master file with all rowsby-party/<name>.csv — rows for each partyby-stock/<name>.csv — rows for each stock itemby-month/YYYY-MM.csv — rows for each monthindex.json — metadata (row count, party list, stock list, sync time)tally queryQuery the local file-tree cache without hitting Tally. Instant results from partitioned CSVs.
# All Zoloto items sold to a specific party
tally query --type sales --party "Jai Chanda" --stock "Zoloto*" --company "ABC"
# Summary of all sales to a party
tally query --type sales --party "Jai Chanda*" --format summary --company "ABC"
# Sales for a date range
tally query --type sales --from 2025-04-01 --to 2025-06-30 --company "ABC"
# JSON output
tally query --type sales --stock "ZOLOTO*" --format json --company "ABC"
| Flag | Purpose |
|---|---|
--company (required) | Selects which company cache to read |
--type | sales or purchase (default: sales) |
--party | Filter by party name (case-insensitive, supports * glob) |
--stock | Filter by stock item name (case-insensitive, supports * glob) |
--from, --to | Date range filter (YYYY-MM-DD) |
--format | Output format: csv (default), json, summary |
--cache-dir | Override cache directory (default .tally-cache/) |
Query strategy: reads the smallest partition file possible (by-party, by-stock, or by-month) then applies remaining filters in-memory.
| Subtype | Useful fetch fields |
|---|---|
| Ledger | Name, Parent, OpeningBalance, ClosingBalance, MailingName, Address, StateName, PinCode, Country, Email, LedgerPhone, LedgerMobile, GSTRegistrationType, PartyGSTIN, IsBillWiseOn |
| Group | Name, Parent, IsRevenue, IsDeemedPositive, AffectsGrossProfit |
| StockItem | Name, Parent, BaseUnits, AdditionalUnits, OpeningBalance, ClosingBalance, OpeningRate, OpeningValue, GSTApplicable, GSTTypeOfSupply |
| Voucher | Date, VoucherTypeName, VoucherNumber, Narration, PartyLedgerName, Amount, LedgerEntries.List, AllInventoryEntries.List |
| Variable | Format |
|---|---|
SVCURRENTCOMPANY | string (set with --company) |
SVFROMDATE | YYYYMMDD (CLI accepts YYYY-MM-DD via --from) |
SVTODATE | YYYYMMDD (--to) |
LedgerName | string (--ledger) |
GroupName | string (--group) |
EXPLODEFLAG | Yes/No (--explode) |
SVEXPORTFORMAT | always set to $$SysName:XML by the CLI |
| Code | Meaning |
|---|---|
| 0 | Success — <STATUS>1</STATUS> |
| 1 | Tally returned <STATUS>0</STATUS> (full envelope still on stdout; reason on stderr) |
| 2 | Tally unreachable / connection refused |
| 3 | Bad CLI args |
| 4 | HTTP timeout |
| 5 | Response not valid XML |
When tally ping fails with exit code 2 (connection refused / unreachable), ask the user:
9000, but it may differ.Then provide these instructions so the user can verify and configure their TallyPrime HTTP server:
How to check/enable the TallyPrime HTTP server:
- In TallyPrime, press F1 (Help).
- Press S to open Settings.
- Press N to open Connectivity.
- Select Client/Server Configuration.
- Set Tally Acts as → Server.
- Set Enable ODBC Server → Yes.
- Note the Port number shown (default
9000) — this is the port to use with--port.- Accept/save the settings and restart TallyPrime if prompted.
Once the user confirms the port, retry with the correct port:
tally ping --port <user-provided-port>
If the port differs from 9000, use --port on all subsequent commands.
When <STATUS>0</STATUS>, Tally returns one of three formats:
Plain text:
<ENVELOPE><HEADER><STATUS>0</STATUS></HEADER><BODY><DATA>DESC not found</DATA></BODY></ENVELOPE>
Structured <LINEERROR>:
<ENVELOPE><HEADER><STATUS>0</STATUS></HEADER><BODY><DATA>
<LINEERROR>Voucher totals do not match!</LINEERROR>...
</DATA></BODY></ENVELOPE>
Structured <STATUS.LIST> with error code:
<ENVELOPE><HEADER><STATUS>0</STATUS></HEADER><BODY><DATA>
<STATUS.LIST><STATUS><CODE>123</CODE><DESC>Invalid Request</DESC></STATUS></STATUS.LIST>
</DATA></BODY></ENVELOPE>
The CLI parses all three formats and surfaces the message on stderr; the full envelope is still on stdout.
In Tally's XML output, balance amounts follow this sign convention:
| Group | Negative value | Positive value |
|---|---|---|
| Sundry Debtors | Debit balance (party owes us — receivable) | Credit balance (we owe them) |
| Sundry Creditors | Credit balance (we owe them — payable) | Debit balance (they owe us) |
⚠️ A negative
ClosingBalanceorBILLCLfor a Sundry Debtor means the party has an outstanding debit balance (they owe us money). Do NOT interpret it as an advance or credit.
Tally companies can have thousands of ledgers, stock items, and vouchers. Always scope your queries to avoid overwhelming the response:
--parent for collections to filter by group hierarchy instead of fetching all items.--fields for collections to return only the fields you need (e.g. --fields Name,ClosingBalance) instead of all properties.--voucher-type when querying Day Book or similar reports to filter by transaction type.--filter for arbitrary TDL filter expressions when you need custom scoping.tally object (single item by name) over tally collection (all items) when you know the specific entity name.--chunk monthly on tally report or tally template when fetching a full financial year from a large company. This splits the request into monthly sub-requests and merges the results, avoiding timeouts.TDL filter expression syntax (for --filter):
$FieldName (e.g. $ClosingBalance, $Name, $VoucherTypeName)=, >, <, >=, <=, <> (not equal)AND, OR, NOT$$IsLedgerProfit, $$IsGroup, etc.$ClosingBalance > 10000 AND $Parent = "Sundry Debtors"templates/ ships ~33 reusable XML request envelopes with placeholders ({{COMPANY}}, {{FROMDATE}} in YYYYMMDD, {{TODATE}}, {{LEDGER}}, {{GROUP}}, {{STOCKITEM}}, {{STOCKGROUP}}, {{VOUCHERTYPE}}, {{VOUCHERNUMBER}}).
The recommended way to invoke them is tally template --name <relative/path> (see above), which handles placeholder substitution, XML escaping, and date conversion for you. Unfilled placeholders generate a warning on stderr.
Filtered collection templates (use these to scope large collections):
| Template | Placeholders | Purpose |
|---|---|---|
collections/list_ledgers_by_group | {{COMPANY}}, {{GROUP}} | Ledgers under a specific group |
collections/list_stock_items_by_group | {{COMPANY}}, {{STOCKGROUP}} | Stock items under a specific stock group |
collections/list_vouchers_by_type | {{COMPANY}}, {{FROMDATE}}, {{TODATE}}, {{VOUCHERTYPE}} | Vouchers filtered by type (Sales, Purchase, etc.) |
collections/list_vouchers_with_items | {{COMPANY}}, {{FROMDATE}}, {{TODATE}}, {{VOUCHERTYPE}} | Vouchers with inventory item details (STOCKITEMNAME, ACTUALQTY, RATE, AMOUNT) |
Custom-TDL collection templates (workarounds for built-in report IDs that crash Tally):
| Template | Placeholders | Why custom |
|---|---|---|
collections/list_currencies | {{COMPANY}} | Built-in List of Currencies crashes Tally; uses TYPE=Currency |
collections/list_units | {{COMPANY}} | Built-in List of Units crashes Tally; uses TYPE=Unit |
collections/list_vouchers_dated | {{COMPANY}}, {{FROMDATE}}, {{TODATE}} | Built-in List of Vouchers crashes Tally; uses TDL TYPE=Voucher collection |
Templates can also be used as references when constructing custom tally raw requests.
For large companies with thousands of vouchers, the CLI supports a local file-tree cache that enables instant queries without hitting Tally repeatedly.
tally sync which internally uses the list_vouchers_with_items Collection-based template with monthly chunking (12 requests for a full FY).Date, Invoice, Party, StockItem, Rate, Discount%, Qty, Amount..tally-cache/<company>/<type>/..tally-cache/
ABC_Company_Ltd/
sales/
all.csv <- every row (50k+ lines for a full year)
by-party/
Customer_One_Delhi.csv
Customer_Two_Mumbai.csv
...
by-stock/
Widget_A_20mm.csv
Pipe_B_50mm.csv
...
by-month/
2025-04.csv
2025-05.csv
...
index.json <- metadata (parties, items, months, row count, sync time)
Each partition CSV has the same header: Date,Invoice,Party,StockItem,Rate,Discount%,Qty,Amount.
File names are sanitized (spaces → underscores, special chars stripped, max 100 chars).
Prefer tally query over live Tally requests when:
.tally-cache/<company>/sales/index.json exists)Use tally sync first if the cache doesn't exist or is stale:
tally sync --type sales --from 2025-04-01 --to 2026-03-31 \
--company "ABC Company Ltd" --host ... --port ...
Then query instantly (no network required):
# Sales of a specific stock group to a party
tally query --type sales --party "Customer One*" --stock "Widget*" --format summary --company "ABC Company Ltd"
# All sales of items matching a pattern
tally query --type sales --stock "Pipe*" --format csv --company "ABC Company Ltd"
# Date-filtered query
tally query --type sales --from 2025-10-01 --to 2025-12-31 --party "Customer*" --company "ABC Company Ltd"
The tally query command picks the smallest partition file to minimize I/O:
--party set → reads from by-party/<sanitized>.csv--stock set → reads from by-stock/<sanitized>.csv--from/--to → reads matching by-month/YYYY-MM.csv filesall.csvRemaining filters (if both party and stock are set) are applied in-memory after loading the partition file.
{
"company": "ABC Company Ltd",
"type": "sales",
"from": "2025-04-01",
"to": "2026-03-31",
"synced_at": "2026-05-07T00:15:00+05:30",
"total_rows": 50000,
"parties": ["Customer One, Delhi", "Customer Two, Mumbai", ...],
"stock_items": ["Widget A 20mm", "Pipe B 50mm", ...],
"months": ["2025-04", "2025-05", ...]
}
Use index.json to check whether a sync is needed and to discover available parties/stock items for autocomplete or validation before querying.
If you encounter any issues with this skill, reach out to Piyush Garg (developer of this skill) at [email protected].
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub piyushgarg-dev/tally-erp-skill --plugin tally-erp