Every YarnTally instance ships with a read-only REST API: pull your inventory and sales into Tally, your ERP, your BI, or your own automation. Outbound webhooks are live — seven events, HMAC-signed. Write endpoints are on the roadmap and are marked as such below.
Generate an API key from Admin → API access (the same list of endpoints appears there). Every request needs an Authorization: Bearer YOUR_KEY header.
curl https://yarntally.mnbresearch.com/api/v1/inventory \
-H "Authorization: Bearer yt_xxxxxxxx_..." \
-H "Content-Type: application/json"The REST API is part of the Business plan. Rate limits: 1,200 req/min on Business, 6,000 req/min on Enterprise.
Five read endpoints. Everything is scoped to the org that owns the API key, and all of it is read-only — nothing in v1 writes to your data.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/inventory | Every lot with kg remaining, SKU, godown and cost. Filters: ?godown_code= ?yarn_type= ?limit= (max 1000) |
| GET | /api/v1/sales | Sales with customer + godown. Defaults to the last 30 days. Filters: ?from=YYYY-MM-DD ?to=YYYY-MM-DD ?limit= (max 1000). Cancelled sales excluded. |
| GET | /api/v1/export/customers | CSV: customers with credit limit, total billed, total paid and outstanding. |
| GET | /api/v1/export/expenses | CSV: expenses by date, category, amount, note and who recorded it. |
| GET | /api/v1/export/mill-payments | CSV: mill invoices and payments per supplier, newest first. |
These do not exist. Every one of them returns 404 today. They are listed so you know what is coming — do not write integration code against them, and email us if one of them is what you need next.
| Method | Path | Planned for |
|---|---|---|
| GET | /api/v1/customers | List customers as JSON |
| POST | /api/v1/customers | Create a customer |
| POST | /api/v1/sales | Create a sale (invoice + stock decrement) |
| POST | /api/v1/purchase-orders | Create a purchase order |
| GET | /api/v1/reports/gstr1 | GSTR-1 formatted export |
Add an endpoint under Admin → API access → Webhooks, pick the events you want, and copy the signing secret — it is shown once, at creation. We POST JSON to your URL (https only) as things happen.
sale.createdFires when a new sale is logged
The moment the Sell screen records a sale. Sent after the stock decrement has committed, so the quantities in the payload are final.
sale.cancelledFires when a sale is cancelled
When an admin or manager cancels a sale and the stock is returned to the lot. Carries the cancellation reason.
sale.dispatchedFires when the order leaves the godown
When a sale is marked IN TRANSIT on the Dispatch board — i.e. the vehicle has left. Marking a sale merely 'loaded' does not fire this, because the goods have not shipped yet.
invoice.paidFires when payment is received against an invoice
Two paths: a payment link raised against a specific sale is marked paid, or a sale is billed with payment status 'paid' at the time of sale. A payment recorded against a customer's ACCOUNT rather than a named invoice does not fire this — YarnTally does not allocate account payments to individual invoices, so naming one would be a guess.
customer.createdFires when a new customer is added
When a customer is created from the Customers screen. Bulk imports do not fire it per-row — see the docs note.
stock.lowFires when a SKU crosses the low-stock threshold
A daily scan, at 06:00 UTC. Fires once when a SKU's days-of-cover first drops below 21, and not again for 14 days unless it recovers and falls back. It is a daily scan rather than an instant trigger because 'days of cover' is derived from a 30-day sales average, which changes without anybody touching that SKU.
compliance.dueFires 7 days before any compliance deadline (LUT, EDPMS, GST)
The same daily scan. Fires once per deadline when it is 7 days or fewer away and still open.
The body is an envelope; data varies by event and carries the record itself. Treat unknown fields as additive — we will add fields, and will not remove or rename them without a new event name.
POST /your/endpoint
Content-Type: application/json
X-YarnTally-Event: sale.created
X-YarnTally-Timestamp: 1790000000
X-YarnTally-Delivery: 6f1c… (id of this attempt, quote it in support mail)
X-YarnTally-Signature: 9ad3… (hex HMAC-SHA256)
{
"event": "sale.created",
"occurred_at": "2026-09-24T09:14:02.881Z",
"org_id": "…",
"data": { "sale": { "id": "…", "invoice_no": "INV-000412", "total": 184500.00, … } }
}Sign the timestamp, a dot, then the raw body — the bytes exactly as you received them, before any JSON parsing. The timestamp is inside the signed material so a captured delivery cannot be replayed at your endpoint forever.
// Verify a YarnTally webhook (Node.js)
import crypto from "crypto";
const SECRET = process.env.YARNTALLY_WEBHOOK_SECRET; // shown once, at creation
export function verify(rawBody, headers) {
const signature = headers["x-yarntally-signature"];
const timestamp = headers["x-yarntally-timestamp"]; // unix seconds
if (!signature || !timestamp) return false;
// Reject replays. We re-send the ORIGINAL timestamp on retries, so allow
// 300 seconds.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
// Sign the timestamp, a dot, then the raw body — bytes exactly as received,
// before any JSON parsing or re-serialising.
const expected = crypto
.createHmac("sha256", SECRET)
.update(timestamp + "." + rawBody)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature);
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}X-YarnTally-Delivery.occurred_at if sequence matters.API keys are scoped to your org via row-level security — you cannot accidentally read another org's data even with a hijacked key. Rotate keys any time from Settings. See our Security page for full details.