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:
https://api.closebee.com- Authentication with OAuth 2.0 client credentials is Live
- Data endpoints under
/v1are 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:
- The business that owns the Tally data signs in to the TallyPlug app.
- The owner opens Developer access, creates a client, chooses which companies and scopes it can read, and copies the secret once.
- The owner gives the
client_idandclient_secretto 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).
/oauth/tokenGet an access token./oauth/revokeRevoke a token (RFC 7009)./.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.
| Field | Required | Value |
|---|---|---|
grant_type | Yes | client_credentials |
scope | No | Space-separated scopes. Leave it out to get every scope the client was granted. |
client_id, client_secret | Only without Basic auth | Your client credentials. |
A successful response:
{
"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
| Scope | Allows |
|---|---|
masters.read | Companies, ledgers, ledger groups, stock items, stock groups, units, godowns and voucher types. |
vouchers.read | Vouchers and their entries. |
inventory.read | Reserved for the stock and inventory endpoints that come later. |
webhooks.manage | Reserved for managing webhook subscriptions, which come later. |
masters.read:contact | Contact 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.
| Claim | Meaning |
|---|---|
iss | Issuer. |
aud | Always tallyplug-api. |
sub | Subject: the client. |
client_id | The client that asked for the token. |
oid | The business (organisation) that owns the data. |
companies | The Tally companies this client may read. |
scope | The scopes granted to this token. |
jti | Unique token id, used for revocation. |
iat, exp | Issued 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
/v1/companiesCompanies this client may read./v1/companies/{company_id}One company.Masters masters.read
Each path below starts with /v1/companies/{company_id}.
/ledgersLedgers./ledgers/{id}One ledger./ledger-groupsLedger groups./stock-itemsStock items./stock-items/{id}One stock item./stock-groupsStock groups./unitsUnits of measure./godownsGodowns./voucher-typesVoucher types.Vouchers vouchers.read
/v1/companies/{company_id}/vouchersVouchers./v1/companies/{company_id}/vouchers/{id}One voucher. id is the Tally GUID.| Filter | Example | Meaning |
|---|---|---|
type | Sales | Voucher type name. |
from, to | 2026-04-01 | Voucher date range, inclusive. |
ledger | a ledger id | Vouchers that touch this ledger. |
updated_since | 2026-10-01T00:00:00Z | Changed 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.
Sample response
Conventions
- Pagination: lists return
{"data": [...], "next_cursor": "..."}. Passcursor=<next_cursor>for the next page. Whennext_cursorisnull, you have everything.limitis 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.
{
"type": "https://api.closebee.com/errors/insufficient-scope",
"title": "Forbidden",
"status": 403,
"detail": "The access token lacks the vouchers.read scope",
"request_id": "3f2c9a7e-..."
}| Status | Meaning | What to do |
|---|---|---|
| 400 | Bad request: a parameter is missing or invalid (invalid-parameter). | Fix the request. detail says which part. |
| 401 | No token (missing-token), or the token is invalid, expired or revoked (invalid-token). | Get a new token and retry once. |
| 403 | The 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. |
| 404 | No 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. |
| 405 | The method is not allowed on this path. | Use the method shown on this page. |
| 429 | Too many requests. | Wait for the seconds in Retry-After, then retry. |
| 503 | Briefly 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 -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 https://api.closebee.com/v1/companies \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"3. Sales vouchers for a month Preview: coming soon
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 -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