✦Developers

How the KERN APIs work

Everything that applies to every call, in both KERN ERP and KERN CRM.

Quick start — your first call in five minutes

  1. In KERN ERP open Settings → API & Integrations (KERN CRM: Integrations → API clients) and press New API client. Pick what it may read or change.
  2. Copy the key. Right under it, the Quick start has curl, JavaScript and Python with your key already filled in — press Try it to make the first call from your browser.
  3. Or from a terminal:
curl https://<your KERN address>/cedge/public/v1/me \ -H "Authorization: Bearer kern_live_…"

Then pick an API in the KERN ERP or KERN CRM reference, or follow a recipe. Building against test data? Use a sandbox key.

Authentication

Every call carries a credential in the Authorization header. Create API clients in KERN ERP under Settings → API & Integrations, or in KERN CRM under Integrations → API clients.

API keys — simplest for scripts and server-to-server integrations. Live keys start kern_live_, sandbox keys kern_test_.

Authorization: Bearer kern_live_ab12cd34ef56_…

OAuth 2.0 client credentials — for apps. Exchange the client ID and secret for a 15-minute access token, then use the token like a key.

POST /cedge/public/v1/oauth/token (KERN CRM: /cedge-crm/public/v1/oauth/token) Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&client_id=kc_…&client_secret=kern_secret_… → { "access_token": "…", "token_type": "Bearer", "expires_in": 900, "scope": "erp.masters.vendors:read …" }

Keys and secrets are shown once. Rotate issues a new one and keeps the old one working for 24 hours. Revoke stops a key, or the whole client (all its keys and tokens), at once. A client can also be limited to IP addresses and given an expiry date. GET /public/v1/me tells you which client, company, environment and scopes a credential has.

Partner apps — “Connect your KERN account”

Building an app many KERN customers will use? Instead of asking each one for an API key, let their admin connect your app in a few clicks (OAuth 2 authorization code with PKCE). Partner apps are registered by Codeverse — contact uswith your app's name, what it does, its return address(es) and the scopes it needs. You get a client_id (kpa_…) and a client_secret.

  1. Send the admin to the Connect page — KERN ERP: https://<server>/connect, KERN CRM: https://<server>/crm/connect — with response_type=code, client_id, redirect_uri (exactly as registered), scope (space-separated, optional — defaults to all you registered), state and a PKCE code_challenge with code_challenge_method=S256.
  2. The admin signs in, sees what your app asks for, chooses the role it works as and presses Connect. KERN sends them back to your redirect_uri with code and your state (or error=access_denied).
  3. From your server, exchange the code within 10 minutes (it works once):
POST https://<server>/cedge/public/v1/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&client_id=kpa_…&client_secret=kern_secret_… &code=kac_…&redirect_uri=https://app.example.com/kern/callback&code_verifier=… → { "access_token": "eyJ…", "token_type": "Bearer", "expires_in": 900, "refresh_token": "kern_rt_…", "scope": "erp.sales.buyer_pos:read", "kern_product": "ERP", "kern_company_id": 12, "kern_company_name": "…" }
  • Call the API with Authorization: Bearer <access_token> — KERN ERP under /cedge/public/v1, KERN CRM under /cedge-crm/public/v1. The token endpoint is the same for both.
  • Access tokens last 15 minutes. Renew with grant_type=refresh_token (plus client_id, client_secret, refresh_token). Each renewal returns a newrefresh token valid for 30 days — store it and throw the old one away. Using an old refresh token again cancels the connection's tokens, and the admin has to connect again.
  • Each connected company is a separate connection: keep its tokens per kern_company_id.
  • The admin can disconnect your app at any time (Settings → API & Integrations → Revoke). Renewal then fails with invalid_grant; ask them to connect again.
  • Everything else — scopes, plan limits, the role's permissions, error codes, rate limits — works exactly as for an API key.

Sandbox

Create the sandbox on the API clients page: a copy of your company with sample data (styles, buyers, suppliers, orders; in KERN CRM also leads, deals and follow-ups). Then give a client a sandbox key(or sandbox OAuth secret). It calls exactly the same APIs, on the sandbox only. Nothing in the sandbox sends emails or messages or uses AI credits, sandbox calls don't count towards your plan, and responses carry X-Kern-Environment: sandbox. Reset it any time for fresh sample data. Sandbox keys have their own limit of 30 calls a minute.

Not a KERN customer yet? Get a demo key

A read-only sandbox key on sample data — try every read API now. We also email it to you. Customers: create keys for your own company on Settings → API & Integrations instead.

Try it

Every API in the reference has a Try it button. Paste a sandbox key (kern_test_…), fill in the parameters and send — the answer appears on the page. Try it only accepts sandbox keys: the KERN servers refuse a live key coming from this site (TRY_IT_SANDBOX_ONLY). Never paste a live key into a web page; if you did, rotate it.

SDKs

Official libraries for JavaScript / TypeScript and Python have a method for every published API and handle sign-in (key or OAuth), the Idempotency-Key on writes (reused on retries), waiting on rate limits, paging, errors with code / fix / docs, and webhook signature checks.

npm install @codeverse/kern-api # JavaScript / TypeScript (Node 18+, browsers) import { KernErp } from "@codeverse/kern-api"; const kern = new KernErp({ baseUrl: "https://<your KERN address>/cedge", apiKey: process.env.KERN_API_KEY }); const po = await kern.api.procurement.purchaseOrders.get(128); for await (const v of kern.paginate(kern.api.masters.vendors.list, { search: "tex" })) console.log(v.name);pip install kern-api # Python 3.9+ from kern import KernErp kern = KernErp("https://<your KERN address>/cedge", api_key=os.environ["KERN_API_KEY"]) po = kern.api.procurement.purchase_orders.get(128) for v in kern.paginate(kern.api.masters.vendors.list, search="tex"): print(v["name"])

KERN CRM: KernCrm with your /cedge-crm address. Methods follow the reference: api.<module>.<resource>.<action>.

Scopes, roles and plans

A call succeeds only if all three allow it:

  • Scope — erp.<module>.<resource>:read or :write (KERN CRM: crm.…). Write includes read; erp.masters.*:read covers a whole module.
  • Acting role — each client acts as a role you choose; it can never do more than that role (KERN ERP checks the role's permission on that screen).
  • Plan — your plan must include the screen.

Records a client creates show it as the creator ("API · client name") in KERN's history.

Writes and retries (Idempotency-Key)

Every POST, PUT, PATCH and DELETE needs an Idempotency-Key header — any unique text up to 120 characters, such as a UUID. If the network drops and you retry with the same key and the same body, you get the first answer back with Idempotent-Replayed: true and nothing is done twice. The same key with a different body is refused (409 IDEMPOTENCY_MISMATCH). Keys are remembered for 24 hours.

Responses and paging

{ "status": "SUCCESS", "message": "…", "data": { … } } { "status": "ERROR", "message": "This API client needs the scope erp.procurement.grns:read.", "code": "SCOPE_MISSING", "fix": "On Settings → API & Integrations, edit the client and give it the scope named in the message.", "docs": "https://www.codeverse.in/developers/guides/#error-scope-missing", "correlationId": "1f3c…", "timestamp": "…" }

Every error carries a stable code, a fix telling you what to do, and a docs link to that code below.

Lists take page (from 0) and size and return content, totalElements, totalPages and last. Many lists also take searchand filters — see each API. A few small lists (colours, units, sizes, warehouses, report catalogue; in KERN CRM trade partners, orders, invoices, payments and price categories) return everything at once as a plain list — the SDKs' paginate handles both. Sorting (sortBy / sortDir) is offered where the API lists those parameters.

Error codes

Check code, never the message text — messages may be reworded; codes don't change in v1.

HTTPcodeMeaningHow to fix it
400VALIDATION_FAILEDThe request isn't valid.Read the message: it names the field to fix. Check the request body against the API reference.
400IDEMPOTENCY_KEY_REQUIREDA write was sent without an Idempotency-Key header.Send an Idempotency-Key header (a new UUID per action) with every POST, PUT, PATCH or DELETE that changes data.
401INVALID_KEYNo valid API key was sent, or the key is for the other KERN product.Send Authorization: Bearer <key>. Check you're calling the right product (/cedge for KERN ERP, /cedge-crm for KERN CRM).
401KEY_REVOKEDThis key was revoked.Create a new key on Settings → API & Integrations.
401KEY_EXPIREDThis key has passed its expiry date, or its 24-hour changeover after a rotation ended.Use the newer key, or create a new one.
401TOKEN_INVALIDThe OAuth access token is invalid or has expired (tokens last 15 minutes).Get a new token from POST /public/v1/oauth/token. Refresh tokens a minute before they expire.
401UNAUTHORIZEDThe call wasn't signed in.Send Authorization: Bearer <key or token>.
403SCOPE_MISSINGThe API client isn't allowed to do this.On Settings → API & Integrations, edit the client and give it the scope named in the message (read, or read & write).
403PERMISSION_DENIEDThe role the client acts as can't do this on that screen.Give the role the permission in Role Permissions, or make the client act as a role that has it.
403PLAN_NOT_ALLOWEDThe company's KERN plan doesn't include this screen.Upgrade the plan, or use another API the plan includes.
403API_NOT_IN_PLANThe company's plan doesn't include API access.Upgrade to a plan with the KERN API.
403PLAN_READ_ONLYThe plan (or trial) allows reading through the API, not changes.Upgrade to a plan with read and write API access.
403IP_NOT_ALLOWEDThe call came from an address that isn't on the client's IP allow-list.Add the address (or its range) to the client's allowed IP addresses, or call from an allowed server.
403CLIENT_REVOKEDThe API client was revoked.Create a new API client.
403CLIENT_SUSPENDEDThe API client is suspended (the message says why — e.g. the plan's client limit).Fix the reason given, or ask the company admin; plan suspensions lift by themselves when the plan allows again.
403CLIENT_EXPIREDThe API client has passed its stop date.Change the client's stop date, or create a new client.
403API_DISABLEDAPI access is switched off for this company.A company admin can turn it on at Settings → API & Integrations.
403COMPANY_INACTIVEThe company's account isn't active.Contact the company admin or Codeverse support.
403SANDBOX_NOT_READYA sandbox key was used before the company's sandbox exists.Create the sandbox on Settings → API & Integrations (it takes a minute or two).
403TRY_IT_SANDBOX_ONLYA live key was used from the developer docs' Try it.Use a sandbox key (kern_test_…) in Try it. Never paste a live key into a web page — if you did, rotate it.
503SANDBOX_PREPARINGKERN CRM's half of the sandbox is being prepared.Wait a few seconds and try again.
404ROUTE_NOT_FOUNDThere's no published API at this path.Check the path in the API reference (paths start /public/v1/).
404NOT_FOUNDThe record doesn't exist in this company.Check the id — list the records first to find it.
405METHOD_NOT_ALLOWEDThe path exists but not for this method.Use one of the methods the API reference lists for this path.
409CONFLICTThe record changed or is in a state that doesn't allow this.Fetch the record again, check its status and try again.
409IDEMPOTENCY_MISMATCHThis Idempotency-Key was already used for a different request.Use a new Idempotency-Key for a new action; reuse a key only to retry the exact same request.
409IDEMPOTENCY_IN_PROGRESSA request with this Idempotency-Key is still running.Wait a moment and retry with the same key — you'll get the first answer back.
413BODY_TOO_LARGEThe request body is larger than 2 MB.Send smaller requests (split large lists into several calls).
422BUSINESS_RULEA KERN business rule stopped the change.Read message, fixes and helpArticle in the response — they say what to do first.
429RATE_LIMITEDToo many calls in a minute.Wait for the Retry-After seconds, then go on. Spread calls out; X-RateLimit-Remaining shows what's left.
429QUOTA_EXCEEDEDThis month's API calls in the plan are used up.Calls start again on the 1st; upgrade the plan for more. X-Kern-Monthly-Remaining shows what's left.
500INTERNALSomething went wrong on our side.Retry later. If it keeps happening, contact support and quote the correlationId (X-Request-Id).

422 errors also come with KERN's own reason codes (for example not enough stock) plus fixes and helpArticle describing what to do first.

Limits

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit you get 429 with Retry-After. Your API calls this month and each client's recent calls (result, error code, time, IP) are on the API clients page in KERN; calls refused for access reasons and sandbox calls don't count. Request bodies can be up to 2 MB.

Your plan sets API access (none, read only, read and write), calls a minute, calls a month and the number of API clients. Responses carry X-Kern-Monthly-Limit and X-Kern-Monthly-Remaining; when the month's calls are used up you get 429 QUOTA_EXCEEDED until the 1st. Company admins are warned at 80 % and 100 %. Trials are read only with 1,000 calls.

Webhooks

Instead of asking every few minutes, let KERN tell you. Add a webhook on the API clients page for one API client, give it an https address and choose events — only events for records that client may read. Events include purchase_order.approved, grn.posted, buyer_po.approved, sales_invoice.issued, packing_slip.shipped, work_order.issued, production_run.completed, store_transfer.received, stock_issue.approved and, in KERN CRM, lead.created, deal.created, deal.stage_changed, invoice.posted, payment.confirmed. Press Send test: a webhook receives events once a test is delivered.

POST https://your-app.example.com/kern-webhook Content-Type: application/json Kern-Event-Id: evt_5zxp4njj4iqpnrgj3wgkwpep Kern-Event-Type: purchase_order.approved Kern-Signature: t=1760000000,v1=5f2b… { "id": "evt_5zxp4njj4iqpnrgj3wgkwpep", "type": "purchase_order.approved", "createdAt": "…", "product": "ERP", "data": { "id": 128, "number": "PO-2026-0128", "status": "APPROVED" }, "links": { "api": "/public/v1/procurement/purchase-orders/128" } }

Answer with any 2xx within 10 seconds, then fetch the full record from links.api with your key. Failed deliveries are retried after 1, 5, 30 and 90 minutes; after 20 failures in a row the webhook is switched off. The same event can arrive twice — use Kern-Event-Id to handle each once. Always check the signature with the signing secret shown when you added the webhook:

// Node.js (use the raw body exactly as received) const crypto = require("crypto"); function verify(rawBody, header, secret) { const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("="))); const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); return Math.abs(Date.now() / 1000 - Number(t)) < 300 && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)); }# Python import hmac, hashlib, time def verify(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest() return abs(time.time() - int(parts["t"])) < 300 and hmac.compare_digest(expected, parts["v1"])

On the API clients page, Deliveries shows every delivery — click one to see the exact headers and body — and Send again resends a missed event.

Keep a copy in sync — the changes feed

GET /public/v1/changes lists records created or changed since a time, across every resource your API client can read — oldest first, each with the path to fetch the full record. Use it instead of re-reading every list, or next to webhooks to catch anything you missed while your server was down.

GET /cedge/public/v1/changes?since=2026-10-01T00:00:00Z&limit=100 Authorization: Bearer kern_live_… → data: { "changes": [ { "resource": "procurement.purchase_orders", "id": 128, "number": "PO-2026-0128", "changedAt": "2026-10-09T10:14:03.512+05:30", "path": "/public/v1/procurement/purchase-orders/128" }, … ], "nextCursor": "MTc5MTQ4…", "hasMore": true, "resources": [ "masters.vendors", "procurement.purchase_orders", … ], "skipped": [] }
  • Send since the first time only (records changed at or after it). After that, send cursor = the nextCursor you saved — you get only what changed after it. Save nextCursorafter you've handled each page; while hasMore is true, ask again straight away.
  • Any read scope is enough to call it. Each resource is included only if the client could read it with a normal GET (scope, plan and role). Narrow it with resources=procurement.purchase_orders,sales.invoices; a resource you name but can't read is listed in skipped with the reason.
  • Deletions aren't listed. A record that changes twice between your calls appears once, with its latest time.
  • limit is 1–500 (default 100). The SDKs follow the cursor for you: changePages() / change_pages().
  • KERN ERP: vendors, products, Buyer POs, sales invoices, purchase orders, GRNs, packing slips, boxes, master boxes, fabric lots, production orders, work orders, production runs, store transfers, stock issues, quality inspections, yarn & fabric POs, vendor payments and line plans. KERN CRM (/cedge-crm/public/v1/changes): leads, deals, follow-ups, sales orders, catalogues, offers, warehouses and transfer orders.

Postman

Download the collection from a reference page, or in Postman choose Import → Link and paste https://www.codeverse.in/developers/download/erp/postman/ (KERN CRM: …/download/crm/postman/). Set the collection variables baseUrl and apiKey — every published API is ready to send.

Troubleshooting

  • Read code and fix in the response — they say what to change.
  • On the API clients page open the client's Recent calls and click a call: request, answer, meaning and fix, for the last 30 days.
  • Usage by API on the same page shows which APIs are called most, fail most and are slowest.
  • 401 with a correct-looking key: check you call the right product (/cedge KERN ERP, /cedge-crm KERN CRM) and that the key wasn't rotated or revoked.
  • Still stuck: quote the correlationId (X-Request-Id) to support.

Versions and changes

This is v1 (/public/v1/…). We may add APIs, fields and optional parameters. We never remove or rename a field, change its type, or make a parameter required in v1 — that would come as v2, announced in the changelog well ahead, with v1 kept running for at least 12 months after. A deprecated API returns a Deprecation header and, once its end date is set, a Sunset header with that date — the reference marks it too. Subscribe to the changelog to be emailed about changes.

Support

Quote the correlationId (or the X-Request-Id response header) and we can find the exact call. Use ? → Report a problem in KERN, or contact us.