Developers

The TallyPlug API

Read the TallyPrime masters and vouchers that TallyPlug syncs to the cloud, from your own software. Sign in with OAuth 2.0 client credentials and call a plain JSON REST API.

Overview

Every request goes to one base URL:

Base URL
https://api.closebee.com
  • Authentication with OAuth 2.0 client credentials is Live
  • Data endpoints under /v1 are Preview: coming soon. The paths and shapes below are the planned contract and may still change before launch.

The data comes from the TallyPrime companies the business has connected through TallyPlug Desktop. The API reads the cloud copy, not Tally directly.

Getting access

Rolling out soon

Business owners create API clients in the TallyPlug app under Developer access. Each client gets a client_id and a client_secret, limited to the Tally companies and scopes the owner picks. The owner can revoke a client at any time.

How to get a client:

  1. The business that owns the Tally data signs in to the TallyPlug app.
  2. The owner opens Developer access, creates a client, chooses which companies and scopes it can read, and copies the secret once.
  3. The owner gives the client_id and client_secret to you, the developer, over a private channel.

There is no self-service developer sign-up on this site, and there is no form here to request keys. Keep the secret on your server. Never ship it in a browser, a mobile app or a public repository.

Authentication Live

Use the OAuth 2.0 client credentials grant (RFC 6749, section 4.4).

POST/oauth/tokenGet an access token.
POST/oauth/revokeRevoke a token (RFC 7009).
GET/.well-known/jwks.jsonPublic keys to verify the token signature.

POST /oauth/token

Send the body as application/x-www-form-urlencoded. Authenticate the client with HTTP Basic auth (preferred), or with client_id and client_secret in the body. Never put the secret in the query string: such requests are rejected.

FieldRequiredValue
grant_typeYesclient_credentials
scopeNoSpace-separated scopes. Leave it out to get every scope the client was granted.
client_id, client_secretOnly without Basic authYour client credentials.

A successful response:

JSON
{
  "token_type": "Bearer",
  "expires_in": 900,
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

The token lasts 15 minutes. There is no refresh token: ask for a new token when the old one is about to expire.

POST /oauth/revoke

Form-encoded, with the same client authentication. Send the token in token, and optionally token_type_hint=access_token.

Scopes

ScopeAllows
masters.readCompanies, ledgers, ledger groups, stock items, stock groups, units, godowns and voucher types.
vouchers.readVouchers and their entries.
inventory.readReserved for the stock and inventory endpoints that come later.
webhooks.manageReserved for managing webhook subscriptions, which come later.
masters.read:contactContact details on masters, such as phone and email. Ask for it only if you need those fields.

The API is read-only. vouchers.write is not issued.

Access token

The access token is a JWT signed with RS256. Send it on every /v1 call as Authorization: Bearer <token>. You can verify it with the keys at /.well-known/jwks.json.

ClaimMeaning
issIssuer.
audAlways tallyplug-api.
subSubject: the client.
client_idThe client that asked for the token.
oidThe business (organisation) that owns the data.
companiesThe Tally companies this client may read.
scopeThe scopes granted to this token.
jtiUnique token id, used for revocation.
iat, expIssued at and expiry, in Unix seconds.

Endpoints Preview: coming soon

These /v1 endpoints are not live yet. You can get a token today, but the data calls are not switched on. Plan against them, and expect small changes before launch.

company_id is a stable id that TallyPlug gives each Tally company. It stays the same when the company is renamed in Tally, so store it instead of the company name.

Companies masters.read

GET/v1/companiesCompanies this client may read.
GET/v1/companies/{company_id}One company.

Masters masters.read

Each path below starts with /v1/companies/{company_id}.

GET/ledgersLedgers.
GET/ledgers/{id}One ledger.
GET/ledger-groupsLedger groups.
GET/stock-itemsStock items.
GET/stock-items/{id}One stock item.
GET/stock-groupsStock groups.
GET/unitsUnits of measure.
GET/godownsGodowns.
GET/voucher-typesVoucher types.

Vouchers vouchers.read

GET/v1/companies/{company_id}/vouchersVouchers.
GET/v1/companies/{company_id}/vouchers/{id}One voucher. id is the Tally GUID.
FilterExampleMeaning
typeSalesVoucher type name.
from, to2026-04-01Voucher date range, inclusive.
ledgera ledger idVouchers that touch this ledger.
updated_since2026-10-01T00:00:00ZChanged since this time, for incremental sync.

Try it Preview

Pick an endpoint, fill in the fields and press Send to see the request as curl and a sample response.

Responses are sample data and nothing is sent: this console makes no network calls.

curl

Sample response

Conventions

  • Pagination: lists return {"data": [...], "next_cursor": "..."}. Pass cursor=<next_cursor> for the next page. When next_cursor is null, you have everything. limit is 100 by default, 500 at most.
  • Amounts are decimal strings, such as "1180.00", so no precision is lost. Parse them with a decimal type, not a float.
  • X-Request-Id: every response carries one. Quote it when you contact support.
  • X-TallyPlug-Synced-At: when the company was last synced from Tally. Data is as fresh as that sync, normally within about 5 minutes, and only while TallyPlug Desktop and Tally are running on the business's PC.
  • Paths never end in .php. Use them exactly as shown.

Errors

On /oauth, errors follow RFC 6749: a JSON body with error (for example invalid_client, invalid_grant, invalid_scope, unsupported_grant_type, invalid_request) and error_description.

On /v1, errors are RFC 9457 problem details, sent as application/problem+json. The type is a URL of the form https://api.closebee.com/errors/<slug>, and request_id matches the X-Request-Id header.

JSON
{
  "type": "https://api.closebee.com/errors/insufficient-scope",
  "title": "Forbidden",
  "status": 403,
  "detail": "The access token lacks the vouchers.read scope",
  "request_id": "3f2c9a7e-..."
}
StatusMeaningWhat to do
400Bad request: a parameter is missing or invalid (invalid-parameter).Fix the request. detail says which part.
401No token (missing-token), or the token is invalid, expired or revoked (invalid-token).Get a new token and retry once.
403The token lacks the scope this call needs (insufficient-scope).Ask for the scope in the token request, or ask the business owner to grant it.
404No such record or path (not-found), or a company this client may not read (company-not-found).Check the ids and the path. For a company, ask the business owner to add it to your client.
405The method is not allowed on this path.Use the method shown on this page.
429Too many requests.Wait for the seconds in Retry-After, then retry.
503Briefly unavailable (unavailable).Wait for the seconds in Retry-After, then retry.

Examples

Replace the placeholders with the credentials the business owner gives you.

1. Get a token Live

curl
curl -X POST https://api.closebee.com/oauth/token \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d "scope=masters.read vouchers.read"

2. List companies Preview: coming soon

curl
curl https://api.closebee.com/v1/companies \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

3. Sales vouchers for a month Preview: coming soon

curl
curl "https://api.closebee.com/v1/companies/YOUR_COMPANY_ID/vouchers?type=Sales&from=2026-09-01&to=2026-09-30&limit=100" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

4. Revoke a token Live

curl
curl -X POST https://api.closebee.com/oauth/revoke \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d token=YOUR_ACCESS_TOKEN \
  -d token_type_hint=access_token