API overview

A versioned REST API over HTTPS, authenticated with OAuth 2.0. Anything that can make an HTTP request can post to TallyPrime.

Base URL

base url
http://localhost:4173/api/v1

v1 is the Balancely API. A new version will only ever appear for a change that would break an existing integration, and it would run alongside v1.

Conventions

  • Every operation is a resource.action key — sales_invoices.create, ledgers.search, vouchers.cancel — called at /api/v1/tally/{key}.

  • Writes are POST with a JSON body. Reads are GET with query parameters; POST works for them too.

  • Every field name, in and out, is snake_case. Dates are YYYY-MM-DD.

  • A record says what it is in object, and a voucher's number is always number.

  • Success is the HTTP status. There is no ok flag to check.

Response shape

One record, a list of records, or an error. Every one carries the request id.

HTTP/1.1 201 Created

{
  "data": {
    "object": "sales_invoice",
    "number": "INV-1042",
    "date": "2026-04-12",
    "customer": "Aurora Traders",
    "amount": 2950,
    "item_count": 1,
    "bill_reference_name": "INV-1042"
  },
  "meta": { "request_id": "req_8f2c…", "operation": "sales_invoices.create", "outcome": "created" }
}

meta.outcome says what a write did: created (status 201), updated, found — an existing record returned unchanged — or, for a cancel, cancelled and unchanged. meta.replayed is true when a retry was answered with the first result.

When TallyPrime saved the record but not every field as sent, data.warnings lists each one — { "code": "field_not_saved", "label": "PAN", "message": "…" }. The record is real either way, so a warning never fails the step. gst_assumed_local means GST was worked out as within your state because the party has no GSTIN or State in TallyPrime; set_by_tally means TallyPrime numbered the voucher itself.

Branch on the code, show the message
code is stable and safe to branch a workflow on. message is written for the person reading it and names the next action — show it as-is rather than replacing it with your own wording.

Pagination

Search operations answer a page at a time. limit is 1 to 100 (default 50). When meta.has_more is true, send meta.next_cursor back as cursor for the next page; the last page has next_cursor: null.

GET
http://localhost:4173/api/v1/tally/ledgers.search?search_text=traders&limit=50
http://localhost:4173/api/v1/tally/ledgers.search?search_text=traders&limit=50&cursor=eyJvIjo1MH0

A search reaches at most the first 1,000 matches; narrow it with a filter to see past them.

Status codes

StatusMeans
200Done: a read, an update, a record found, a cancel.
201A new record was created in TallyPrime.
400The request is malformed or a required field is missing. error.field names it.
401No access token, or it is not valid.
402The plan's monthly allowance is used up.
403The token lacks the permission this operation needs.
404No such operation, list, or record.
405A write was sent as GET.
409It conflicts with what TallyPrime already holds — a duplicate, an existing record, another company open.
422TallyPrime's data cannot accept it — an unknown ledger, an unbalanced entry, a refused voucher.
429Too many requests from this application. Wait Retry-After seconds.
503The connector or TallyPrime is not reachable right now. Retry after Retry-After seconds.
502 / 504TallyPrime did not confirm in time. Re-send the same request: it updates the same record.

Tracing a request

Every response carries meta.request_id, also sent as the X-Request-Id header. Quote it when reporting a problem and we can find the exact request in our logs.

Endpoints

PathPurpose
/api/v1/oauthAuthorization, token exchange, refresh, revoke and userinfo.
/api/v1/tally/{operation}Run one operation: create entries and read from a connected company.
/api/v1/tally/batchRun up to 50 operations in one call.
/api/v1/tally/lists/{name}Dropdown sources for a step editor — customers, ledgers, stock items, godowns.
/api/v1/tally/operationsThe catalogue of operations and lists. Public, so a connector can build its own step picker. Add ?fields=1 for every field and a working example of each.

Something missing on this page? Tell us and we will fix it.