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
- In your Shopify admin, open StockIQ → Settings → API Keys.
- Click Create key, name it (e.g. "Warehouse sync"), pick the scopes (permissions) it needs, and optionally an expiry.
- 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 thenext_cursorfrom 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=senton purchase orders,?location_id=loc_4on inventory). - Idempotency (important for writes): send an
Idempotency-Key: <unique-id>header on anyPOST/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 areYYYY-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.
- Create a subscription (scope
webhooks:manage):
The response includes a signing secret (shown once).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"] }' - 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" } } - Verify every webhook using the
X-Kernvel-Signature: t=<unix>,v1=<hmac>header — computeHMAC-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.