Integrations

API documentation

Pull scanned invoice, purchase order and bank-entry JSON straight into Tally, Zoho Books, Zoho Inventory, SAP Business One, SAP S/4HANA, Busy, Marg, QuickBooks, Xero or any custom in-house software using two simple REST endpoints. Sign in to generate an API key — the key in the examples below is masked for safety.

Uniform JSON via Replacement Rules

Every scan is normalised through your org's Rename JSON Variables rules before delivery. Rules cover two levels:

  • Variable names: map many raw labels (e.g. "Supplier", "Party Name", "Vendor Name") to one canonical key like vendor_name.
  • Values: map many raw values to one clean value — e.g. state: "MH", "M.H.", "Maharastra" → "Maharashtra".
  • Vendor and Product mapping tables auto-normalise vendor_name and product_name in the response.

The result: every downstream system (Tally, Zoho, SAP, custom ERP) receives the same keys and value spellings, so no per-invoice mapping logic is needed on your side.

Authentication

Every request must include an API key. Sign in and visit the API keys page to create one. Send it as a Bearer token in the Authorization header. Keys are scoped to a single user and are shown in full only once at creation time — store yours securely.

Authorization: Bearer sp_live_••••••••••••••••••••••••••••••

Alternatively send x-api-key: sp_live_•••••••••••••••••••••••••••••• if Bearer headers are not supported by your client.

Setup in Tally (one-time)

The end-user flow is just two steps: Upload Document and Sync Tally to upload invoice. To enable the sync side, do this one-time setup:

  1. Copy your API key — sign in, open API keys and copy your default key (or create a new one).
  2. Install our Tally TDL — ping us on WhatsApp to receive the TDL file, then load it inside Tally (Gateway → F1: Manage Local TDL → point to the .tdl file).
  3. Paste the API key into the TDL prompt — open the TDL menu inside Tally and paste your key when prompted. Tally will remember it.
  4. Hit Sync — the TDL calls GET /scans/unsynced, posts each invoice as a bill, then calls POST /scans/mark-synced so the same invoice never imports twice.

After setup, your users only ever do two things: drop an invoice in the chat box (or upload it) and click Sync inside Tally.

GET /api/public/v1/scans/unsynced

Returns every successful scan that has not yet been marked as synced. Duplicates by invoice_no are removed (the oldest record wins); scans without an invoice number are all returned.

Query parameters

  • limit — optional, default 500, max 1000.
  • category — optional, one of vendor_invoice, purchase_order, or bank_entry. Omit to return all categories.

Request

curl 'https://scan.innosate.com/api/public/v1/scans/unsynced?category=vendor_invoice&limit=200' \
  -H 'Authorization: Bearer sp_live_••••••••••••••••••••••••••••••'

Response 200

{
  "count": 2,
  "items": [
    {
      "id": "5d2c7b8a-1c1a-4a25-9b6d-1c43f2c1aa01",
      "invoice_no": "INV-1042",
      "filename": "1042.pdf",
      "category": "vendor_invoice",
      "created_at": "2026-06-15T09:11:43.221Z",
      "data": {
        "invoice_metadata": { "invoice_no": "INV-1042", "invoice_date": "2026-06-15", "gstin": "27ABCDE1234F1Z5", "name": "Acme Traders", "...": "..." },
        "line_items": [
          { "description": "Widget A", "hsn": "8471", "qty": 10, "rate": 250, "amount": 2500 }
        ]
      }
    },
    {
      "id": "f2e8c1d2-aa11-4d6c-93b7-2f8e21a90b22",
      "invoice_no": "INV-1043",
      "filename": "1043.pdf",
      "created_at": "2026-06-15T09:18:02.001Z",
      "data": { "invoice_metadata": { "invoice_no": "INV-1043" }, "line_items": [] }
    }
  ]
}

POST /api/public/v1/scans/mark-synced

Mark scans as synced so they will not be returned by the unsynced endpoint again. Provide either ids, invoice_nos, or both. Already-synced rows are ignored.

Body (JSON)

{
  "ids": ["5d2c7b8a-1c1a-4a25-9b6d-1c43f2c1aa01"],
  "invoice_nos": ["INV-1043"],
  "source": "tally-tdl"
}

Request

curl -X POST 'https://scan.innosate.com/api/public/v1/scans/mark-synced' \
  -H 'Authorization: Bearer sp_live_••••••••••••••••••••••••••••••' \
  -H 'Content-Type: application/json' \
  -d '{"invoice_nos":["INV-1042","INV-1043"],"source":"tally-tdl"}'

Response 200

{ "ok": true, "updated": 2, "synced_at": "2026-06-17T11:24:08.512Z" }

Vendors API

Manage the canonical vendor list and aliases used to match vendor names on scanned bank_entry rows. Only an API key owned by the reseller (org owner) can create or update vendors; any authenticated key can list.

GET /api/public/v1/vendors

curl 'https://scan.innosate.com/api/public/v1/vendors' \
  -H 'Authorization: Bearer sp_live_••••••••••••••••••••••••••••••'
{
  "count": 1,
  "items": [
    {
      "id": "a1c...",
      "name": "Acme Traders Pvt Ltd",
      "updated_at": "2026-06-15T09:11:43.221Z",
      "aliases": [{ "id": "…", "alias": "ACME TRADERS" }, { "id": "…", "alias": "acme-traders" }]
    }
  ]
}

POST /api/public/v1/vendors

Creates the vendor if the name is new, or reuses the existing vendor with that name. Provided aliases are appended (duplicates collapsed by normalized value).

{
  "name": "Acme Traders Pvt Ltd",
  "aliases": ["ACME TRADERS", "acme-traders", "Acme Trdrs"]
}
curl -X POST 'https://scan.innosate.com/api/public/v1/vendors' \
  -H 'Authorization: Bearer sp_live_••••••••••••••••••••••••••••••' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Acme Traders Pvt Ltd","aliases":["ACME TRADERS","acme-traders"]}'
{ "ok": true, "id": "a1c...", "created": true, "aliases_added": 2 }

Products API

Manage the canonical product list and aliases used to normalise product names on invoice line items. Only a reseller (org owner) key can create or update; any authenticated key can list.

GET /api/public/v1/products

curl 'https://scan.innosate.com/api/public/v1/products' \
  -H 'Authorization: Bearer sp_live_••••••••••••••••••••••••••••••'
{
  "count": 1,
  "items": [
    {
      "id": "p1c...",
      "name": "Widget A",
      "updated_at": "2026-06-15T09:11:43.221Z",
      "aliases": [{ "id": "…", "alias": "WIDGET-A" }, { "id": "…", "alias": "widget a 10mm" }]
    }
  ]
}

POST /api/public/v1/products

{
  "name": "Widget A",
  "aliases": ["WIDGET-A", "widget a 10mm", "wdgt-a"]
}
curl -X POST 'https://scan.innosate.com/api/public/v1/products' \
  -H 'Authorization: Bearer sp_live_••••••••••••••••••••••••••••••' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Widget A","aliases":["WIDGET-A","widget a 10mm"]}'
{ "ok": true, "id": "p1c...", "created": false, "aliases_added": 2 }

Errors

  • 401 — missing, invalid, or revoked API key.
  • 400 — invalid JSON body, or neither ids nor invoice_nos provided.
  • 500 — unexpected server error; safe to retry.
{ "error": "Invalid API key." }

Recommended sync flow

  1. Call GET /scans/unsynced to fetch pending records (filter by category if needed).
  2. Post each items[].data JSON into your target system (Tally, Zoho, SAP, custom ERP…).
  3. Collect the successfully imported ids (or invoice_nos).
  4. Call POST /scans/mark-synced with that list so the same records are not delivered again.
  5. Repeat on a schedule (e.g. every 5 minutes via cron, Windows Task Scheduler, n8n, Make, Zapier, or your own worker).

Integrating with Zoho, SAP & custom software

The API is plain REST + JSON so it plugs into any accounting or ERP system that can make an HTTP call. Below are ready-to-adapt snippets. In every case the pattern is the same: GET /scans/unsynced → push into the target system → POST /scans/mark-synced.

Tally (TDL / Prime)

Use our shipped TDL (see setup above) or write your own HTTP Post using Tally's HTTP Post Requests action. The JSON matches Tally's Voucher / Purchase Bill XML mapping fields (party, invoice_no, date, ledger, GST, line items).

Zoho Books / Zoho Inventory (Node.js)

// 1. Pull vendor invoices from ScanParse
const res = await fetch('https://scan.innosate.com/api/public/v1/scans/unsynced?category=vendor_invoice', {
  headers: { Authorization: 'Bearer sp_live_••••••••••••••••••••••••••••••' },
});
const { items } = await res.json();

// 2. Push each one to Zoho Books as a Bill
for (const s of items) {
  const md = s.data.invoice_metadata ?? {};
  await fetch(`https://www.zohoapis.in/books/v3/bills?organization_id=${ORG_ID}`, {
    method: 'POST',
    headers: {
      Authorization: `Zoho-oauthtoken ${ZOHO_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      vendor_name: md.name,
      bill_number: md.invoice_no,
      date: md.invoice_date,
      line_items: (s.data.line_items ?? []).map((li) => ({
        name: li.product_name ?? li.description,
        quantity: li.qty,
        rate: li.rate,
      })),
    }),
  });
}

// 3. Mark them synced so we don't re-import
await fetch('https://scan.innosate.com/api/public/v1/scans/mark-synced', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sp_live_••••••••••••••••••••••••••••••',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ ids: items.map((i) => i.id), source: 'zoho-books' }),
});

SAP Business One / S/4HANA (Service Layer)

# 1. Fetch purchase orders from ScanParse
curl 'https://scan.innosate.com/api/public/v1/scans/unsynced?category=purchase_order' \
  -H 'Authorization: Bearer sp_live_••••••••••••••••••••••••••••••' -o pending.json

# 2. For each item, POST to SAP B1 Service Layer PurchaseOrders endpoint
#    (or use OData v4 API_PURCHASEORDER_PROCESS_SRV on S/4HANA)
curl -X POST 'https://sap-b1:50000/b1s/v1/PurchaseOrders' \
  -H "Cookie: B1SESSION=$SESSION" \
  -H 'Content-Type: application/json' \
  -d @po.json

# 3. Mark synced
curl -X POST 'https://scan.innosate.com/api/public/v1/scans/mark-synced' \
  -H 'Authorization: Bearer sp_live_••••••••••••••••••••••••••••••' \
  -H 'Content-Type: application/json' \
  -d '{"ids":["<uuid>"],"source":"sap-b1"}'

Custom software (Python)

import requests

API = "https://scan.innosate.com/api/public/v1"
KEY = "sp_live_••••••••••••••••••••••••••••••"
H   = {"Authorization": f"Bearer {KEY}"}

# 1. Pull any category: vendor_invoice | purchase_order | bank_entry
pending = requests.get(f"{API}/scans/unsynced", params={"category": "bank_entry"},
                       headers=H).json()

for s in pending["items"]:
    # 2. Push into your ERP / DB / accounting engine
    my_erp.post_bank_entry(s["data"])   # your integration code

# 3. Confirm
requests.post(f"{API}/scans/mark-synced",
              headers={**H, "Content-Type": "application/json"},
              json={"ids": [i["id"] for i in pending["items"]],
                    "source": "custom-erp"})

No-code (Zapier / Make / n8n / Power Automate)

  • Trigger: Schedule every 5 minutes.
  • Step 1: HTTP GET https://scan.innosate.com/api/public/v1/scans/unsynced?category=vendor_invoice with the Authorization header.
  • Step 2: Iterate items[] and map data.invoice_metadata + data.line_items to your target app's "Create Bill" / "Create Invoice" action.
  • Step 3: HTTP POST https://scan.innosate.com/api/public/v1/scans/mark-synced with the imported ids.

Need a ready-made connector for a system not listed here? Ping us on WhatsApp — we ship new integrations continuously.

Data retention (30 days)

Uploaded documents and the JSON we generate from them are stored for 30 days, then permanently deleted by a daily cleanup job. This applies to both synced and unsynced scans.

  • GET /scans/unsynced only ever returns scans from the last 30 days — older records won't reappear.
  • Sync to Tally (or download the JSON) before the 30-day window closes if you need to keep a copy.
  • Credits and account history are not affected — only the document and its JSON payload are removed.