✦Developers

Recipes

The integrations our customers build most, step by step — which APIs and webhooks to use, in which order.

Every recipe starts the same way: create an API client with the scopes shown, try it with a sandbox key, then switch to a live key.

Send approved purchase orders to your accounting app

API client needs: erp.procurement.purchase_orders:read

  1. Add a webhook for the client with the event purchase_order.approved and press Send test.
  2. When an event arrives, check its signature, then fetch the full purchase order from links.api.
  3. Create the bill / purchase in your accounting app. Store the KERN PO id against it so a repeated event (same Kern-Event-Id) is ignored.
GET /cedge/public/v1/procurement/purchase-orders/128 Authorization: Bearer kern_live_… → data: { "poNum": "PO-2026-0128", "vendorName": "…", "lines": [ … ], "grandTotal": … }

Add grn.posted as well to record goods received against the bill.

Bring web-store orders into KERN ERP as Buyer POs

API client needs: erp.sales.buyer_pos:write, erp.products.products:read, erp.masters.vendors:read

  1. Look up the customer (GET /public/v1/masters/vendors?search=…) and each product / SKU (GET /public/v1/products/products?search=…) once, and keep their KERN ids.
  2. Create the Buyer PO. Use your own order number as the Idempotency-Key — if your store sends the order twice, KERN creates it once.
POST /cedge/public/v1/sales/buyer-pos Authorization: Bearer kern_live_… Idempotency-Key: webshop-order-100245 Content-Type: application/json { "vendorId": 31, "poNo": "100245", "txnDate": "2026-10-09", "deliveryDate": "2026-10-20", "lines": [ { "productId": 812, "sizeCode": "M", "qty": 4 } ] }

Subscribe to buyer_po.approved and packing_slip.shipped to update the order in your store as it moves.

Keep your web store's stock up to date

API client needs: erp.inventory.fg_stock:read, erp.packing.packing_slips:read

  1. Once a night, read finished-goods stock page by page and update your store.
  2. In between, listen for packing_slip.shipped and store_transfer.received and re-read the SKUs involved.
GET /cedge/public/v1/inventory/fg-stock?page=0&size=200 → data: { "content": [ { "sku": "TSH-NVY-M", "qty": 140, … } ], "totalPages": 7 }

Mind the rate limit: read pages one after another and respect Retry-After if you get 429.

Send website enquiries to KERN CRM as leads

API client needs: crm.leads.leads:write

  1. When someone fills in your contact form, create a lead in KERN CRM from your server (never from the browser — the key must stay secret).
  2. Optionally add a webhook for lead.created to notify your team chat.
POST /cedge-crm/public/v1/leads/leads Authorization: Bearer kern_live_… Idempotency-Key: form-7f3a… Content-Type: application/json { "name": "Asha Rao", "company": "Rao Garments", "email": "asha@example.com", "phone": "+919800000000", "source": "Website" }

A duplicate phone or email is refused with a clear message — show it, or add "allowDuplicate": true on purpose.

Record production from the shop floor

API client needs: erp.production.runs:write

  1. Read the run and its stages: GET /public/v1/production/runs/{id}.
  2. Before posting, check what each stage can take: GET /public/v1/production/runs/stages/{runStageId}/available-input.
  3. Post the day's figures per size. KERN checks them against what the earlier stage produced and moves the run on.
POST /cedge/public/v1/production/runs/stages/551/daily-entries Idempotency-Key: line3-2026-10-09-stage551 Content-Type: application/json { "entryDate": "2026-10-09", "sizes": [ { "sizeCode": "M", "inputQty": 120, "outputQty": 116, "rejectQty": 4 } ] }

Add a webhook for production_run.completed to know when the run is done.

Keep your own database in step with KERN

API client needs: read scopes for the resources you copy, e.g. erp.procurement.*:read, erp.sales.*:read

  1. First run: call GET /public/v1/changes?since=… with the date you want to start from.
  2. For each change, fetch the record from its path and save it in your database by resource + id (insert or update).
  3. After each page, save nextCursor. While hasMore is true, ask again with cursor= that value.
  4. From then on, run it every few minutes (or when a webhook arrives) with the saved cursor — you only get what changed.
GET /cedge/public/v1/changes?cursor=MTc5MTQ4…&resources=procurement.purchase_orders,sales.invoices → data: { "changes": [ { "resource": "sales.invoices", "id": 77, "path": "/public/v1/sales/invoices/77", … } ], "nextCursor": "MTc5MTQ5…", "hasMore": false }

Deleted records aren't listed — if you need to notice them, re-check your copy against the list APIs now and then.

Building something else?