API v1 · REST · JSON

Build on YarnTally

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.

Authentication

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.

Endpoints available now

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.

MethodPathDescription
GET/api/v1/inventoryEvery lot with kg remaining, SKU, godown and cost. Filters: ?godown_code= ?yarn_type= ?limit= (max 1000)
GET/api/v1/salesSales 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/customersCSV: customers with credit limit, total billed, total paid and outstanding.
GET/api/v1/export/expensesCSV: expenses by date, category, amount, note and who recorded it.
GET/api/v1/export/mill-paymentsCSV: mill invoices and payments per supplier, newest first.

Not built yetPlanned endpoints

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.

MethodPathPlanned for
GET/api/v1/customersList customers as JSON
POST/api/v1/customersCreate a customer
POST/api/v1/salesCreate a sale (invoice + stock decrement)
POST/api/v1/purchase-ordersCreate a purchase order
GET/api/v1/reports/gstr1GSTR-1 formatted export

WebhooksLive

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.created

Fires 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.cancelled

Fires 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.dispatched

Fires 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.paid

Fires 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.created

Fires 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.low

Fires 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.due

Fires 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.

What a delivery looks like

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, … } }
}

Verifying the signature

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);
}

Retries, ordering and the small print

Security first

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.

Ready to build?

The REST API and webhooks are part of the Business plan.