← Back to StockIQ overview
S

Kernvel StockIQ API Reference

The developer reference for the StockIQ public API — read inventory, forecasts and alerts, manage suppliers and purchase orders, and subscribe to webhooks.

StockIQ API Reference

The StockIQ API lets you (or your developer) read and manage your store's StockIQ data programmatically — build custom dashboards, sync forecasts and stock levels into your ERP or warehouse system, push purchase orders into your accounting tool, or automate reordering workflows. Everything you can do in the app, you can increasingly do by API.

New to APIs? You don't have to use this — the app does everything from its dashboard. The API is for merchants who want to build custom integrations. If you have a developer, hand them this page; it's all they need.

Base URL

https://stockiq.kernvellabs.com/api/public/v1

All endpoints are under /api/public/v1. All traffic is HTTPS only.

Getting an API key

  1. In your Shopify admin, open StockIQ → Settings → API Keys.
  2. Click Create key, name it (e.g. "Warehouse sync"), pick the scopes (permissions) it needs, and optionally an expiry.
  3. Copy the key immediately — it's shown only once. Store it somewhere safe (a password manager or your server's secrets). If you lose it, revoke it and make a new one.

Keys look like kv_stk_live_xxxxxxxx (live) or kv_stk_test_xxxxxxxx (test mode).

Authentication

Send your key as a Bearer token on every request:

curl https://stockiq.kernvellabs.com/api/public/v1/inventory \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"

(X-API-Key: kv_stk_live_xxxxxxxx works too.) Your key is tied to your store — you never pass a shop name. Unauthenticated requests get 401.

Test mode

Use a *_test_* key to build and experiment safely: test mode never sends real emails, never charges, never creates real purchase orders with your suppliers. Test keys are available on every plan, so you can build before you upgrade. Test responses include the header X-Kernvel-Test-Mode: true.

Permissions (scopes)

Each key only does what you allow. Scopes are resource:action, e.g. inventory:read, purchase_orders:write. A request missing the needed scope returns 403 insufficient_scope telling you which scope to add. Grant the least your integration needs.

StockIQ's scopes:

Scope Lets the key…
inventory:read Read per-location stock levels and AI risk metrics.
forecasts:read Read demand forecasts.
demand_signals:read Read intra-day demand signals.
purchase_orders:read Read purchase orders and their line items.
purchase_orders:write Create and update purchase orders.
suppliers:read Read suppliers and their performance scores.
suppliers:write Create and update suppliers.
alerts:read Read inventory alerts (low-stock, stockout risk, anomalies).
account:read Read your plan, usage, and API limits.
webhooks:manage Create and manage webhook subscriptions.

StockIQ is read-heavy by design: inventory, forecasts, demand signals, and alerts are read-only over the API (they're computed by StockIQ's AI from your store data). Purchase orders and suppliers are the writable resources.

Plans

API access is part of your StockIQ plan. In general: Free = test keys only; Starter = read access on live keys; Growth/Scale/Enterprise = full read/write + webhooks. If an endpoint isn't in your plan you'll get 403 feature_not_in_plan with an upgrade link. Your key can never hold a scope your plan doesn't include — the effective scope set is what you requested intersected with what your tier allows.

Requests

  • Send JSON bodies with Content-Type: application/json.
  • Pagination: list endpoints return up to 25 items by default (max 100). Use ?limit= and the next_cursor from the response to page:
    curl "https://stockiq.kernvellabs.com/api/public/v1/inventory?limit=50&cursor=c2Vfb..." \
      -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
    
  • Filtering/sorting: each list endpoint documents its filters (e.g. ?status=sent on purchase orders, ?location_id=loc_4 on inventory).
  • Idempotency (important for writes): send an Idempotency-Key: <unique-id> header on any POST/PATCH. If a request is retried (network hiccup), we return the original result instead of doing it twice — so a retried purchase order is never created twice. Required on writes that move state (creating a PO).

Responses

  • A single item is returned directly; lists are wrapped:
    { "object": "list", "data": [ { "id": "inv_8fk3", "object": "inventory_level", "...": "..." } ],
      "has_more": true, "next_cursor": "c2Vfb..." }
    
  • Money is a string with a currency ("total": "1240.00", "currency": "USD"). Times are UTC ISO-8601 (2026-06-23T10:00:00Z); pure dates are YYYY-MM-DD. IDs are stable strings with a type prefix (inv_, fcst_, po_, sup_, …).
  • Every response includes X-Request-Id (quote it in support tickets) and rate-limit headers (below).

Rate limits

Each key has a per-minute limit that scales with your plan. Every response includes:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1750671600

If you exceed it you get 429 with a Retry-After header (seconds to wait). Write endpoints have their own lower limits, noted on each.

Errors

Errors use a standard machine-readable format (application/problem+json):

{ "type": "https://docs.kernvellabs.com/api/errors/insufficient_scope",
  "title": "Insufficient scope", "status": 403, "code": "insufficient_scope",
  "detail": "This key is missing the required scope 'purchase_orders:write'.",
  "errors": [ { "field": "scope", "message": "Required: purchase_orders:write" } ],
  "request_id": "req_8fk3a1" }

Common codes: invalid_request, validation_failed, invalid_api_key, expired_api_key, insufficient_scope, feature_not_in_plan, not_found, idempotency_conflict, conflict, rate_limited, internal_error. The detail, errors, and request_id fields tell you what to fix and what to quote if you need support.

Webhooks (get notified instead of polling)

Rather than polling for new forecasts or alerts, subscribe to events and we'll POST them to your URL the moment they happen.

  1. Create a subscription (scope webhooks:manage):
    curl https://stockiq.kernvellabs.com/api/public/v1/webhooks -X POST \
      -H "Authorization: Bearer kv_stk_live_xxxxxxxx" \
      -H "Content-Type: application/json" \
      -d '{ "url": "https://yourapp.com/hooks/stockiq", "events": ["alert.raised"] }'
    
    The response includes a signing secret (shown once).
  2. We POST an event envelope:
    { "id": "evt_8fk3", "type": "alert.raised", "api_version": "2026-06-23",
      "created_at": "2026-06-23T10:00:00Z", "data": { "...": "the resource" } }
    
  3. Verify every webhook using the X-Kernvel-Signature: t=<unix>,v1=<hmac> header — compute HMAC-SHA256(secret, "<t>.<raw-body>") and compare; reject if the timestamp is older than 5 minutes.

We retry failed deliveries with backoff and show a delivery log; persistent failures auto-disable the endpoint and email you. See Webhook events for the full catalog.

Versioning

The API is versioned in the URL (/api/public/v1). We won't make breaking changes to v1 — new fields may be added, but nothing is removed without a new version and 90+ days notice. The X-Kernvel-Api-Version response header shows the dated version applied.

Tools

  • OpenAPI spec: https://stockiq.kernvellabs.com/api/public/v1/openapi.json (import into your tooling).
  • Postman collection: download the StockIQ collection, or export the latest one from the API Keys page.
  • Quickstart, errors, and webhooks: this reference includes key setup, request format, error shape, signature verification, and event catalog in one page.

Need help?

Email support@kernvellabs.com with the request_id from the response — that lets us find your exact call instantly.


Inventory

Per-location stock levels for every product StockIQ tracks, enriched with the AI metrics StockIQ computes nightly: days until stockout, overstock flags, reorder points, and the optimal max stock level. This is the read surface for "how much do I have, where, and is it about to run out."

GET /inventory

List inventory levels across your locations. One row per product-per-location, newest first.

Scope: inventory:read

Param Type Description
limit integer Items per page, 1–100 (default 25).
cursor string next_cursor from the previous page.
location_id string Filter to a single location (e.g. loc_4).
curl "https://stockiq.kernvellabs.com/api/public/v1/inventory?location_id=loc_4&limit=50" \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "inventory_level",
      "id": "inv_50231",
      "product_id": "prod_182",
      "location_id": "loc_4",
      "available": 12,
      "committed": 4,
      "incoming": 0,
      "damaged": 0,
      "safety_stock": 20,
      "reorder_point": 25,
      "reorder_quantity": 100,
      "max_stock_level": 300,
      "days_until_stockout": 3.4,
      "overstock_flag": false,
      "updated_at": "2026-06-23T06:12:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Notable errors: 403 insufficient_scope (key lacks inventory:read), 400 invalid_request (unknown location_id format).

GET /inventory/{id}

Fetch a single inventory level by its id.

Scope: inventory:read

curl https://stockiq.kernvellabs.com/api/public/v1/inventory/inv_50231 \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "inventory_level",
  "id": "inv_50231",
  "product_id": "prod_182",
  "location_id": "loc_4",
  "available": 12,
  "committed": 4,
  "incoming": 0,
  "damaged": 0,
  "safety_stock": 20,
  "reorder_point": 25,
  "reorder_quantity": 100,
  "max_stock_level": 300,
  "days_until_stockout": 3.4,
  "overstock_flag": false,
  "updated_at": "2026-06-23T06:12:00Z"
}

Notable errors: 404 not_found (no such level, or it belongs to another store — we return 404, never 403, so we don't reveal another store's data exists).

JavaScript (fetch) example — list inventory at one location:

const res = await fetch(
  "https://stockiq.kernvellabs.com/api/public/v1/inventory?location_id=loc_4",
  { headers: { Authorization: "Bearer kv_stk_live_xxxxxxxx" } }
);
if (!res.ok) throw new Error(`StockIQ ${res.status}: ${(await res.json()).detail}`);
const { data } = await res.json();
for (const level of data) {
  console.log(`${level.product_id}: ${level.available} left, ~${level.days_until_stockout} days to stockout`);
}

Forecasts

StockIQ's AI demand forecasts — the predicted demand for each tracked product over a horizon (default 30 days), with confidence bounds, the model version, and a plain-language explanation. The single-forecast endpoint additionally returns the per-day daily_predictions breakdown. This is the read surface for "how much will I sell, and why."

GET /forecasts

List demand forecasts, newest first.

Scope: forecasts:read

Param Type Description
limit integer Items per page, 1–100 (default 25).
cursor string next_cursor from the previous page.
product_id string Filter to a single product (e.g. prod_182).
curl "https://stockiq.kernvellabs.com/api/public/v1/forecasts?product_id=prod_182" \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "forecast",
      "id": "fcst_90112",
      "product_id": "prod_182",
      "location_id": "loc_4",
      "forecast_date": "2026-06-23",
      "forecast_horizon_days": 30,
      "model_version": "v1",
      "total_predicted_demand": 412.0,
      "confidence_lower": 360.0,
      "confidence_upper": 470.0,
      "confidence_score": 88.0,
      "explanation": "Demand is trending up ~8% on a rising Google Trends signal for this category.",
      "created_at": "2026-06-23T05:40:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Notable errors: 403 feature_not_in_plan (forecasts read access requires a paid plan), 403 insufficient_scope.

GET /forecasts/{id}

Fetch a single forecast, including the daily_predictions array (per-day predicted/lower/upper).

Scope: forecasts:read

curl https://stockiq.kernvellabs.com/api/public/v1/forecasts/fcst_90112 \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "forecast",
  "id": "fcst_90112",
  "product_id": "prod_182",
  "location_id": "loc_4",
  "forecast_date": "2026-06-23",
  "forecast_horizon_days": 30,
  "model_version": "v1",
  "total_predicted_demand": 412.0,
  "confidence_lower": 360.0,
  "confidence_upper": 470.0,
  "confidence_score": 88.0,
  "daily_predictions": [
    { "date": "2026-06-24", "predicted": 13.5, "lower": 11.0, "upper": 16.0 },
    { "date": "2026-06-25", "predicted": 14.1, "lower": 11.6, "upper": 16.8 }
  ],
  "explanation": "Demand is trending up ~8% on a rising Google Trends signal for this category.",
  "created_at": "2026-06-23T05:40:00Z"
}

Notable errors: 404 not_found.

JavaScript (fetch) example — pull the latest forecast for a product:

const res = await fetch(
  "https://stockiq.kernvellabs.com/api/public/v1/forecasts?product_id=prod_182&limit=1",
  { headers: { Authorization: "Bearer kv_stk_live_xxxxxxxx" } }
);
const { data } = await res.json();
const f = data[0];
console.log(`Next ${f.forecast_horizon_days}d: ${f.total_predicted_demand} units (±, ${f.confidence_score}% confidence)`);

Demand Signals

The intra-day demand stream StockIQ senses from your store: each order, cart-add, page-view, search, or return is recorded as a signal with its strength, quantity, and source. This is the read surface for "what's happening right now" — the raw stream that feeds the forecasts.

GET /demand_signals

List recent demand signals, newest first.

Scope: demand_signals:read

Param Type Description
limit integer Items per page, 1–100 (default 25).
cursor string next_cursor from the previous page.
product_id string Filter to a single product (e.g. prod_182).
curl "https://stockiq.kernvellabs.com/api/public/v1/demand_signals?product_id=prod_182" \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "demand_signal",
      "id": "dsig_771204",
      "product_id": "prod_182",
      "signal_type": "order",
      "signal_strength": 1.0,
      "quantity": 3,
      "source": "shopify",
      "recorded_at": "2026-06-23T09:58:12Z",
      "created_at": "2026-06-23T09:58:13Z"
    }
  ],
  "has_more": true,
  "next_cursor": "c2Vfb3JkZXJfNzcxMjA0"
}

Notable errors: 403 insufficient_scope (key lacks demand_signals:read), 400 invalid_request (bad product_id format).

GET /demand_signals/{id}

Fetch a single demand signal by its id.

Scope: demand_signals:read

curl https://stockiq.kernvellabs.com/api/public/v1/demand_signals/dsig_771204 \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "demand_signal",
  "id": "dsig_771204",
  "product_id": "prod_182",
  "signal_type": "order",
  "signal_strength": 1.0,
  "quantity": 3,
  "source": "shopify",
  "recorded_at": "2026-06-23T09:58:12Z",
  "created_at": "2026-06-23T09:58:13Z"
}

Notable errors: 404 not_found.

JavaScript (fetch) example — pull today's demand signals for a product:

const res = await fetch(
  "https://stockiq.kernvellabs.com/api/public/v1/demand_signals?product_id=prod_182",
  { headers: { Authorization: "Bearer kv_stk_live_xxxxxxxx" } }
);
const { data } = await res.json();
data.forEach(s =>
  console.log(`${s.signal_type}: qty ${s.quantity} from ${s.source} at ${s.recorded_at}`)
);

Purchase Orders

Your purchase orders to suppliers — header (supplier, status, financials, payment terms) plus line items (product, quantities ordered/received, unit cost). This is a writable resource: create and update POs over the API to drive reordering from your own systems. AI-suggested POs raised by StockIQ are returned here too.

GET /purchase_orders

List purchase orders, newest first.

Scope: purchase_orders:read

Param Type Description
limit integer Items per page, 1–100 (default 25).
cursor string next_cursor from the previous page.
status string draft, sent, confirmed, partially_received, received, or cancelled.
curl "https://stockiq.kernvellabs.com/api/public/v1/purchase_orders?status=sent" \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "purchase_order",
      "id": "po_4821",
      "po_number": "PO-0142",
      "supplier_id": "sup_12",
      "status": "sent",
      "order_date": "2026-06-20",
      "expected_delivery_date": "2026-07-04",
      "subtotal": "1200.00",
      "tax": "0.00",
      "shipping_cost": "40.00",
      "discount": "0.00",
      "total": "1240.00",
      "currency": "USD",
      "payment_status": "unpaid",
      "payment_terms": "Net 30",
      "items": [],
      "created_at": "2026-06-20T14:02:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET /purchase_orders/{id}

Fetch a single purchase order, including its items array.

Scope: purchase_orders:read

curl https://stockiq.kernvellabs.com/api/public/v1/purchase_orders/po_4821 \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "purchase_order",
  "id": "po_4821",
  "po_number": "PO-0142",
  "supplier_id": "sup_12",
  "status": "sent",
  "order_date": "2026-06-20",
  "expected_delivery_date": "2026-07-04",
  "subtotal": "1200.00",
  "tax": "0.00",
  "shipping_cost": "40.00",
  "discount": "0.00",
  "total": "1240.00",
  "currency": "USD",
  "payment_status": "unpaid",
  "payment_terms": "Net 30",
  "items": [
    {
      "object": "purchase_order_item",
      "id": "poi_9001",
      "product_id": "prod_182",
      "quantity_ordered": 100,
      "quantity_received": 0,
      "unit_cost": "12.00",
      "total_cost": "1200.00"
    }
  ],
  "created_at": "2026-06-20T14:02:00Z"
}

Notable errors: 404 not_found.

POST /purchase_orders

Create a purchase order. Returns 201 Created. Idempotent — send an Idempotency-Key header so a retry never creates a duplicate PO.

Scope: purchase_orders:write

Field Type Required Description
supplier_id string yes The supplier to order from (e.g. sup_12).
destination_location_id string no Where the stock is received (e.g. loc_4).
expected_delivery_date string (date) no YYYY-MM-DD.
shipping_cost number no Shipping charge (≥ 0, default 0).
tax number no Tax amount (≥ 0, default 0).
discount number no Discount amount (≥ 0, default 0).
payment_terms string no e.g. Net 30.
shipping_method string no e.g. Express.
notes string no Free-text note sent with the PO.
items array yes At least one line item (see below).
items[].product_id string yes The product to order (e.g. prod_182).
items[].quantity_ordered integer yes Units to order (> 0).
items[].unit_cost number yes Cost per unit (≥ 0).
curl https://stockiq.kernvellabs.com/api/public/v1/purchase_orders -X POST \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8f0c1a2e-7b3d-4c11-9a2e-3f5b6c7d8e90" \
  -d '{
    "supplier_id": "sup_12",
    "destination_location_id": "loc_4",
    "expected_delivery_date": "2026-07-04",
    "payment_terms": "Net 30",
    "items": [
      { "product_id": "prod_182", "quantity_ordered": 100, "unit_cost": 12.00 }
    ]
  }'
{
  "object": "purchase_order",
  "id": "po_4830",
  "po_number": "PO-0143",
  "supplier_id": "sup_12",
  "status": "draft",
  "expected_delivery_date": "2026-07-04",
  "subtotal": "1200.00",
  "tax": "0.00",
  "shipping_cost": "0.00",
  "discount": "0.00",
  "total": "1200.00",
  "currency": "USD",
  "payment_status": "unpaid",
  "payment_terms": "Net 30",
  "items": [
    {
      "object": "purchase_order_item",
      "id": "poi_9020",
      "product_id": "prod_182",
      "quantity_ordered": 100,
      "quantity_received": 0,
      "unit_cost": "12.00",
      "total_cost": "1200.00"
    }
  ],
  "created_at": "2026-06-23T10:30:00Z"
}

Notable errors: 422 unprocessable (unknown supplier_id/product_id format, no items, or a business-rule violation such as a quantity below the supplier's minimum order quantity), 409 idempotency_conflict (same Idempotency-Key, different body), 403 feature_not_in_plan (writes require Scale or Enterprise).

PATCH /purchase_orders/{id}

Update a purchase order — typically to advance its status (e.g. draft → sent) or amend dates/terms. Idempotent.

Scope: purchase_orders:write

Field Type Description
status string New status (sent, confirmed, cancelled, …). State changes are validated.
expected_delivery_date string (date) YYYY-MM-DD.
shipping_cost number Shipping charge (≥ 0).
tax number Tax amount (≥ 0).
discount number Discount amount (≥ 0).
shipping_method string e.g. Express.
payment_status string e.g. unpaid, paid.
notes string Free-text note.
curl https://stockiq.kernvellabs.com/api/public/v1/purchase_orders/po_4830 -X PATCH \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 1c9d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f" \
  -d '{ "status": "sent", "shipping_method": "Express" }'
{
  "object": "purchase_order",
  "id": "po_4830",
  "po_number": "PO-0143",
  "supplier_id": "sup_12",
  "status": "sent",
  "expected_delivery_date": "2026-07-04",
  "subtotal": "1200.00",
  "total": "1200.00",
  "currency": "USD",
  "payment_status": "unpaid",
  "items": [],
  "created_at": "2026-06-23T10:30:00Z"
}

Notable errors: 409 conflict (illegal state transition, e.g. editing a received PO), 404 not_found.

JavaScript (fetch) example — create a PO with idempotency:

const res = await fetch("https://stockiq.kernvellabs.com/api/public/v1/purchase_orders", {
  method: "POST",
  headers: {
    Authorization: "Bearer kv_stk_live_xxxxxxxx",
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID()
  },
  body: JSON.stringify({
    supplier_id: "sup_12",
    items: [{ product_id: "prod_182", quantity_ordered: 100, unit_cost: 12.00 }]
  })
});
if (!res.ok) {
  const err = await res.json();
  throw new Error(`${err.code}: ${err.detail}`);
}
const po = await res.json();
console.log(`Created ${po.po_number} (${po.total} ${po.currency})`);

Suppliers

Your suppliers — contact and terms (lead time, minimum order quantity, payment terms, currency) plus the AI performance score StockIQ computes from PO history (overall_score) and a risk_level. This is a writable resource.

GET /suppliers

List suppliers, newest first.

Scope: suppliers:read

Param Type Description
limit integer Items per page, 1–100 (default 25).
cursor string next_cursor from the previous page.
is_active boolean true returns only active suppliers; false only inactive.
curl "https://stockiq.kernvellabs.com/api/public/v1/suppliers?is_active=true" \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "supplier",
      "id": "sup_12",
      "name": "Pacific Textiles Co.",
      "email": "orders@pacifictextiles.example",
      "phone": "+84 28 1234 5678",
      "website": "https://pacifictextiles.example",
      "contact_person": "Mei Lin",
      "city": "Ho Chi Minh City",
      "country": "Vietnam",
      "lead_time_days": 21,
      "minimum_order_quantity": 50,
      "minimum_order_value": "500.00",
      "payment_terms": "Net 30",
      "currency": "USD",
      "overall_score": 90.0,
      "risk_level": "low",
      "total_orders": 47,
      "is_active": true,
      "created_at": "2026-02-11T08:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET /suppliers/{id}

Fetch a single supplier.

Scope: suppliers:read

curl https://stockiq.kernvellabs.com/api/public/v1/suppliers/sup_12 \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"

Notable errors: 404 not_found.

POST /suppliers

Create a supplier.

Scope: suppliers:write

Field Type Required Description
name string yes Supplier name (1–255 chars).
email string no Contact email (must be unique within your store if set).
phone string no Contact phone.
website string no Supplier website.
contact_person string no Primary contact name.
address string no Street address.
city string no City.
country string no Country.
lead_time_days integer no Typical lead time in days, 1–365 (default 14).
minimum_order_quantity integer no MOQ for this supplier (≥ 1).
minimum_order_value number no Minimum order value (≥ 0).
payment_terms string no e.g. Net 30, COD.
currency string no 3-letter code (default USD).
curl https://stockiq.kernvellabs.com/api/public/v1/suppliers -X POST \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809" \
  -d '{
    "name": "Pacific Textiles Co.",
    "email": "orders@pacifictextiles.example",
    "country": "Vietnam",
    "lead_time_days": 21,
    "payment_terms": "Net 30"
  }'
{
  "object": "supplier",
  "id": "sup_18",
  "name": "Pacific Textiles Co.",
  "email": "orders@pacifictextiles.example",
  "country": "Vietnam",
  "lead_time_days": 21,
  "payment_terms": "Net 30",
  "currency": "USD",
  "is_active": true,
  "created_at": "2026-06-23T10:45:00Z"
}

Notable errors: 422 unprocessable (missing name or other invalid field), 403 feature_not_in_plan.

PATCH /suppliers/{id}

Update a supplier's contact details or terms. Send is_active: false to deactivate a supplier.

Scope: suppliers:write

curl https://stockiq.kernvellabs.com/api/public/v1/suppliers/sup_18 -X PATCH \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "lead_time_days": 18, "minimum_order_quantity": 100 }'
{
  "object": "supplier",
  "id": "sup_18",
  "name": "Pacific Textiles Co.",
  "lead_time_days": 18,
  "minimum_order_quantity": 100,
  "currency": "USD",
  "is_active": true,
  "created_at": "2026-06-23T10:45:00Z"
}

Notable errors: 404 not_found, 422 unprocessable (invalid field).

JavaScript (fetch) example — surface high-risk suppliers:

const res = await fetch(
  "https://stockiq.kernvellabs.com/api/public/v1/suppliers?is_active=true",
  { headers: { Authorization: "Bearer kv_stk_live_xxxxxxxx" } }
);
const { data } = await res.json();
data
  .filter(s => s.risk_level === "high")
  .forEach(s => console.log(`${s.name}: overall ${s.overall_score}, risk ${s.risk_level}`));

Alerts

The inventory alerts StockIQ raises — low stock, stockout risk, actual stockouts, overstock, demand spikes/drops, supplier delays, anomalies, dead stock, and reorder-needed — each with a severity, safe operational trigger data, and StockIQ's recommended action. This is the read surface for "what needs my attention right now."

data is intentionally minimized on the public API. It includes operational fields such as stock counts, product ids, source refs, and pricing thresholds, but strips shopper emails, phone numbers, customer names, Shopify customer ids, consent metadata, and free-form shopper notes. Source-backed alerts can include source_app, source_ref, and source_label so you can tell when an alert came from a sister app such as Image SearchIQ.

GET /alerts

List alerts, newest first.

Scope: alerts:read

Param Type Description
limit integer Items per page, 1–100 (default 25).
cursor string next_cursor from the previous page.
alert_type string e.g. low_stock, stockout_risk, overstock, demand_spike, supplier_delay, anomaly, dead_stock, reorder_needed.
severity string low, medium, high, or critical.
curl "https://stockiq.kernvellabs.com/api/public/v1/alerts?severity=critical" \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "alert",
      "id": "alrt_66120",
      "product_id": "prod_182",
      "alert_type": "stockout_risk",
      "severity": "critical",
      "title": "Black Tee (M) will stock out in ~2 days",
      "message": "Available 12, selling ~6/day across 2 locations.",
      "data": { "current_stock": 12, "daily_sales": 6, "days_until_stockout": 2.0 },
      "source_app": null,
      "source_ref": null,
      "source_label": null,
      "recommendation": "Reorder 120 units from Pacific Textiles Co. (21-day lead time).",
      "recommended_action": "reorder",
      "is_read": false,
      "is_dismissed": false,
      "is_actioned": false,
      "created_at": "2026-06-23T07:00:00Z"
    }
  ],
  "has_more": true,
  "next_cursor": "c2VfYWxlcnRfNjYxMjA"
}

GET /alerts/{id}

Fetch a single alert.

Scope: alerts:read

curl https://stockiq.kernvellabs.com/api/public/v1/alerts/alrt_66120 \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"

Notable errors: 404 not_found.

JavaScript (fetch) example — page through all critical alerts:

let url = "https://stockiq.kernvellabs.com/api/public/v1/alerts?severity=critical&limit=100";
const headers = { Authorization: "Bearer kv_stk_live_xxxxxxxx" };
const alerts = [];
while (url) {
  const res = await fetch(url, { headers });
  const page = await res.json();
  alerts.push(...page.data);
  url = page.has_more
    ? `https://stockiq.kernvellabs.com/api/public/v1/alerts?severity=critical&limit=100&cursor=${page.next_cursor}`
    : null;
}
console.log(`${alerts.length} critical alerts need action`);

Account

Read your store's plan, status, and API capabilities — useful for confirming which scopes and limits your key has before you build against them.

GET /account

Return the store's plan, subscription status, currency, and its API-access block (key allowance, grantable scopes, rate limit, webhook-endpoint allowance).

Scope: account:read

curl https://stockiq.kernvellabs.com/api/public/v1/account \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "account",
  "id": "shop_84",
  "shop_domain": "demo-store.myshopify.com",
  "plan": "scale",
  "subscription_status": "active",
  "currency": "USD",
  "test_mode": false,
  "api_access": {
    "live_keys": true,
    "max_keys": 25,
    "scopes": ["account:read", "webhooks:manage", "inventory:read", "forecasts:read", "demand_signals:read", "purchase_orders:read", "purchase_orders:write", "suppliers:read", "suppliers:write", "alerts:read"],
    "rate_per_min": 300,
    "webhook_endpoints": 5
  }
}

GET /account/usage

Return a usage + rate-limit snapshot: your plan, your plan's resource limits, and your current rate-limit window.

Scope: account:read

curl https://stockiq.kernvellabs.com/api/public/v1/account/usage \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "usage",
  "plan": "scale",
  "max_products": 10000,
  "max_forecasts_per_month": 500,
  "max_purchase_orders": 500,
  "max_suppliers": 100,
  "rate_limit": 300,
  "rate_limit_remaining": 298
}

JavaScript (fetch) example — check the key's scopes before using it:

const res = await fetch("https://stockiq.kernvellabs.com/api/public/v1/account", {
  headers: { Authorization: "Bearer kv_stk_live_xxxxxxxx" }
});
const acct = await res.json();
if (!acct.api_access.scopes.includes("purchase_orders:write")) {
  console.warn("This key cannot create purchase orders — add the scope in StockIQ → Settings → API Keys.");
}

Notable errors: 403 insufficient_scope (key lacks account:read).


Webhooks

Manage the endpoints StockIQ POSTs events to. Create a subscription to get the signing secret (shown once), list and inspect your subscriptions, and delete ones you no longer need. The destination URL is validated at creation time (must be https and resolve to a public address), and the number of active endpoints is capped by your plan.

POST /webhooks

Create a webhook subscription. New subscriptions return 201. The response includes the signing_secret once — store it; you'll need it to verify deliveries.

Exact retries with the same URL, live/test environment, and event set return 200 with the existing subscription and an Idempotent-Replay: true response header. The signing secret is NOT re-returned on a replay (it is shown only once, on the original 201 create) — reuse the secret you stored then.

Scope: webhooks:manage

Field Type Required Description
url string yes Your HTTPS endpoint (public address only).
events array yes One or more event types (see Webhook events).
description string no A label for your own reference.
curl https://stockiq.kernvellabs.com/api/public/v1/webhooks -X POST \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/hooks/stockiq",
    "events": ["forecast.ready", "alert.raised", "restock.detected"],
    "description": "ERP sync"
  }'
{
  "object": "webhook_subscription",
  "id": "whk_310",
  "url": "https://yourapp.com/hooks/stockiq",
  "events": ["forecast.ready", "alert.raised", "restock.detected"],
  "description": "ERP sync",
  "env": "live",
  "is_active": true,
  "signing_secret": "whsec_3f8a...redacted",
  "created_at": "2026-06-23T11:20:00Z"
}

Notable errors: 400 invalid_request (non-HTTPS or private URL), 422 invalid_request (unknown event type in events), 403 feature_not_in_plan (webhooks require Scale or Enterprise, or you've hit your endpoint cap).

GET /webhooks

List your webhook subscriptions (secrets are never returned).

Scope: webhooks:manage

curl https://stockiq.kernvellabs.com/api/public/v1/webhooks \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "webhook_subscription",
      "id": "whk_310",
      "url": "https://yourapp.com/hooks/stockiq",
      "events": ["forecast.ready", "alert.raised", "restock.detected"],
      "description": "ERP sync",
      "env": "live",
      "is_active": true,
      "created_at": "2026-06-23T11:20:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET /webhooks/{id}

Fetch a single webhook subscription (the signing secret is never returned).

Scope: webhooks:manage

curl https://stockiq.kernvellabs.com/api/public/v1/webhooks/whk_310 \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{
  "object": "webhook_subscription",
  "id": "whk_310",
  "url": "https://yourapp.com/hooks/stockiq",
  "events": ["forecast.ready", "alert.raised", "restock.detected"],
  "description": "ERP sync",
  "env": "live",
  "is_active": true,
  "created_at": "2026-06-23T11:20:00Z"
}

Notable errors: 404 not_found.

DELETE /webhooks/{id}

Delete a subscription. Deliveries stop immediately.

Scope: webhooks:manage

curl https://stockiq.kernvellabs.com/api/public/v1/webhooks/whk_310 -X DELETE \
  -H "Authorization: Bearer kv_stk_live_xxxxxxxx"
{ "object": "webhook_subscription", "id": "whk_310", "deleted": true }

Notable errors: 404 not_found.

JavaScript (fetch) example — subscribe to forecast and alert events:

const res = await fetch("https://stockiq.kernvellabs.com/api/public/v1/webhooks", {
  method: "POST",
  headers: {
    Authorization: "Bearer kv_stk_live_xxxxxxxx",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    url: "https://yourapp.com/hooks/stockiq",
    events: ["forecast.ready", "alert.raised"]
  })
});
const sub = await res.json();
console.log("Store this secret now — it won't be shown again:", sub.signing_secret);

Webhook events

Subscribe to these event types so your systems react the moment StockIQ acts, instead of polling. Every delivery is a signed envelope; verify the X-Kernvel-Signature header before trusting the body (see Webhooks).

Event Fires when… data payload
forecast.ready A new demand forecast finishes computing for a product. A forecast object.
alert.raised StockIQ raises an inventory alert (low stock, stockout risk, anomaly, …). An alert object.
purchase_order.created A purchase order is created (by you via API/app, or AI-suggested). A purchase_order object.
purchase_order.updated A purchase order changes (status, dates, receipts). A purchase_order object.
restock.detected A product's aggregate stock crosses from out-of-stock back to available. A restock object (product + new available quantity).

Event envelope (every webhook):

{
  "id": "evt_77af3",
  "type": "alert.raised",
  "api_version": "2026-06-23",
  "created_at": "2026-06-23T07:00:01Z",
  "data": {
    "object": "alert",
    "id": "alrt_66120",
    "product_id": "prod_182",
    "alert_type": "stockout_risk",
    "severity": "critical",
    "title": "Black Tee (M) will stock out in ~2 days",
    "data": { "current_stock": 12, "daily_sales": 6, "days_until_stockout": 2.0 },
    "recommendation": "Reorder 120 units from Pacific Textiles Co. (21-day lead time).",
    "recommended_action": "reorder",
    "created_at": "2026-06-23T07:00:00Z"
  }
}

restock.detected payload (illustrating the cross-app restock signal):

{
  "id": "evt_88bc4",
  "type": "restock.detected",
  "api_version": "2026-06-23",
  "created_at": "2026-06-23T12:30:00Z",
  "data": {
    "object": "restock",
    "product_id": "prod_182",
    "sku": "TS-BLK-M",
    "available": 140,
    "previous_available": 0,
    "detected_at": "2026-06-23T12:30:00Z"
  }
}

Test-mode subscriptions (created with a *_test_* key) receive only test-mode events. Failed deliveries are retried with exponential backoff; an endpoint that keeps failing is auto-disabled and you're emailed.