← Back to AuctionAI overview
A

Kernvel AI Auction API Reference

The developer REST API for reading and managing your store's auctions, bids, and webhooks.

Kernvel AI Auction — API Reference

The Kernvel AI Auction API lets you (or your developer) read and manage your store's auction data programmatically — build custom dashboards, sync auctions with another system, place bids from your own front-end, or automate 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://auctionai.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 Kernvel AI Auction → API Keys (in the app's left navigation).
  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_auc_live_xxxxxxxx (live) or kv_auc_test_xxxxxxxx (test mode). The auc segment marks them as Kernvel AI Auction keys.

Authentication

Send your key as a Bearer token on every request:

curl https://auctionai.kernvellabs.com/api/public/v1/auctions \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx"

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

The only route that needs no key is GET /ping (a liveness check that returns no store data).

Test mode

Use a *_test_* key to build and experiment safely: test mode never sends real emails, never charges, never creates real orders or draft orders. Test keys are available on every plan, so you can build before you upgrade. Test responses include the header X-Kernvel-Test-Mode: true, and test-mode webhook subscriptions only receive test-mode events.

Permissions (scopes)

Each key only does what you allow. Scopes are resource:action, e.g. auctions:read, auctions:write. A request missing the needed scope returns 403 insufficient_scope telling you which scope to add. Grant the least your integration needs. Customer and winner contact details sit behind their own PII scopes and are only available on higher plans.

The scopes Kernvel AI Auction recognises:

Scope What it allows
account:read Read plan, usage and limits for the store.
auctions:read Read auctions.
auctions:write Create and update auctions.
auctions:manage Cancel auctions and other sensitive auction ops.
bids:read Read bids on the store's auctions.
bids:write Place bids.
bidders:manage Ban and unban bidders.
products:read Read products available for auction.
webhooks:manage Create, list and delete webhook subscriptions.
winners:read Read auction winners (contact details are PII).
customers:read Read customer contact details (PII).

PII scopes (winners:read, customers:read) are never granted by default — you must explicitly check the box when minting the key, and they're only available on top plans.

Plans

API access is part of your Kernvel AI Auction plan, enforced two ways: which scopes a plan can grant, and how many requests per minute it allows.

Plan Live keys Scopes available Webhooks Rate limit
Free Test keys only read-only (account, auctions, bids, products) — 60 / min
Starter Yes read-only (+ winners:read) — 120 / min
Pro Yes full read + write + manage + webhooks + PII up to 5 endpoints 300 / min
Enterprise Yes full read + write + manage + webhooks + PII up to 20 endpoints 600 / min

A key can never hold a scope its plan doesn't grant — the effective scope set is always what you requested ∩ what the plan allows. Ask for an out-of-plan scope (e.g. auctions:write on Starter) and you'll get 403 feature_not_in_plan with an upgrade pointer. Live API access also requires an active subscription; a cancelled or expired plan loses live keys (test keys keep working).

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://auctionai.kernvellabs.com/api/public/v1/auctions?limit=50&cursor=WyJudWxsIiw0Ml0" \
      -H "Authorization: Bearer kv_auc_live_xxxxxxxx"
    
    Cursors are opaque — pass back the exact next_cursor string we return; don't construct your own. A tampered cursor returns 400 invalid_request.
  • Filtering: the auctions list documents its status filter (?status=active).
  • Idempotency (important for writes): send an Idempotency-Key: <unique-id> header on any POST/PATCH/DELETE. If a request is retried (network hiccup), we return the original result instead of doing it twice — so a retried bid or auction never happens twice. Required on money/state actions like placing a bid. Replaying the same key with a different body returns 409 idempotency_conflict. Stored results live for 24 hours.

Responses

  • A single item is returned directly; lists are wrapped:
    { "object": "list",
      "data": [ { "id": "auc_8123", "object": "auction", "...": "..." } ],
      "has_more": true,
      "next_cursor": "WyJudWxsIiw0Ml0" }
    
  • Every object carries an "object" type tag and a string id with a type prefix (auc_, bid_, prod_, whk_, shop_).
  • Money is a decimal string with an explicit currency ("current_price": "120.00", "currency": "USD") — never a float. Times are UTC ISO-8601 with a trailing Z (2026-06-23T10:00:00Z).
  • Every response includes X-Request-Id (quote it in support tickets), the X-Kernvel-Api-Version header, and rate-limit headers (below).

Rate limits

Each key has a per-minute limit that scales with your plan (60 / 120 / 300 / 600 by tier — see Plans). Every response includes:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 298
X-RateLimit-Reset: 1750671600

If you exceed it you get 429 rate_limited with a Retry-After header (seconds to wait).

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 'auctions:write'.",
  "errors": [ { "field": "scope", "message": "Required: auctions:write" } ],
  "request_id": "req_8fk3a1" }

The codes you may see:

HTTP code When it fires
400 invalid_request Malformed request — bad cursor, unknown filter, bad webhook URL.
400 validation_failed A field failed validation; see errors[].
401 invalid_api_key Key missing, malformed, revoked, or for another app.
401 expired_api_key The key's expiry has passed.
403 insufficient_scope The key lacks the scope this route needs.
403 feature_not_in_plan The scope/feature isn't in your plan, or your subscription isn't active.
404 not_found No such resource — or it belongs to another store.
409 idempotency_conflict Same Idempotency-Key, different body.
409 conflict The resource is in a conflicting state (e.g. bidding on an ended auction).
422 unprocessable The request was understood but couldn't be processed (e.g. invalid auction parameters).
429 rate_limited Per-minute limit exceeded; wait Retry-After seconds.
500 internal_error Something went wrong on our side (no detail leaked; quote the request_id).
503 service_unavailable The public API is temporarily disabled.

A cross-store reference returns 404, not 403 — we never confirm whether another store's auc_… exists.

Webhooks (get notified instead of polling)

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

  1. Create a subscription (scope webhooks:manage, available on Pro and Enterprise):
    curl https://auctionai.kernvellabs.com/api/public/v1/webhooks -X POST \
      -H "Authorization: Bearer kv_auc_live_xxxxxxxx" \
      -H "Content-Type: application/json" \
      -d '{ "url": "https://yourapp.com/hooks/auctionai", "events": ["auction.created"] }'
    
    The response includes a signing secret (whsec_…, shown once).
  2. We POST an event envelope:
    { "id": "evt_8fk3", "type": "auction.created", "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.

See Webhook events for the full list and a verification snippet. We retry failed deliveries with backoff (1m, 5m, 30m, 2h, 6h) and auto-disable an endpoint after 15 consecutive failures.

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 (currently 2026-06-23).

Tools

  • OpenAPI spec: https://auctionai.kernvellabs.com/api/public/v1/openapi.json (import into your tooling).
  • Postman collection: download the AuctionAI collection, or export the latest one from the API Keys page.
  • Liveness: GET https://auctionai.kernvellabs.com/api/public/v1/ping (no auth).

Need help?

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


Resources

Kernvel AI Auction exposes five resources. Auctions is the full reference resource (read + write + idempotent state changes); Bids are nested under an auction; Products, Account, and Webhooks round it out.

Resource Endpoints Scopes
Auctions list, get, create, update, cancel auctions:read / auctions:write / auctions:manage
Bids list, place bids:read / bids:write
Products list, get products:read
Account get, usage account:read
Webhooks create, list, get, delete webhooks:manage

Auctions

Your auctions — the heart of the API. Read them, create them, change them, and cancel them. Every write is idempotent and emits a webhook event.

The auction object:

{
  "object": "auction",
  "id": "auc_8123",
  "title": "Vintage Omega Seamaster",
  "status": "active",
  "auction_type": "standard",
  "currency": "USD",
  "current_price": "320.00",
  "starting_price": "100.00",
  "reserve_price": "300.00",
  "buy_now_price": "650.00",
  "final_price": null,
  "total_bids": 14,
  "unique_bidders": 6,
  "product_id": "prod_412",
  "starts_at": "2026-06-20T09:00:00Z",
  "ends_at": "2026-06-24T09:00:00Z",
  "ended_at": null,
  "created_at": "2026-06-19T18:30:00Z"
}
Field Meaning
status One of draft, scheduled, active, ending_soon, ended (has a winner, awaiting payment), sold (paid), cancelled, failed (no bids / reserve not met).
auction_type standard, proxy, popcorn, penny, dutch, or sealed.
current_price The live high bid (or the starting price if no bids yet).
final_price Set once the auction ends with a winner; null while live.
total_bids / unique_bidders Live counters.
product_id The Kernvel product this auction sells (prod_…); see Products.

GET /auctions

List your store's auctions, newest first. Cursor-paginated.

Scope: auctions:read

Param In Type Notes
status query string Filter: active, scheduled, ended, cancelled, failed, or draft. active includes ending_soon; ended includes sold.
limit query int 1–100, default 25.
cursor query string The next_cursor from the previous page.
curl "https://auctionai.kernvellabs.com/api/public/v1/auctions?status=active&limit=2" \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "auction",
      "id": "auc_8123",
      "title": "Vintage Omega Seamaster",
      "status": "active",
      "auction_type": "standard",
      "currency": "USD",
      "current_price": "320.00",
      "starting_price": "100.00",
      "reserve_price": "300.00",
      "buy_now_price": "650.00",
      "final_price": null,
      "total_bids": 14,
      "unique_bidders": 6,
      "product_id": "prod_412",
      "starts_at": "2026-06-20T09:00:00Z",
      "ends_at": "2026-06-24T09:00:00Z",
      "ended_at": null,
      "created_at": "2026-06-19T18:30:00Z"
    }
  ],
  "has_more": true,
  "next_cursor": "WyJudWxsIiw4MTIzXQ"
}

Notable errors: 400 invalid_request (unknown status value or bad cursor), 403 insufficient_scope.

GET /auctions/{auction_id}

Fetch a single auction by id.

Scope: auctions:read

Param In Type Notes
auction_id path string The auction id, e.g. auc_8123.
curl https://auctionai.kernvellabs.com/api/public/v1/auctions/auc_8123 \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx"

Returns the auction object shown above.

Notable errors: 404 not_found (no such auction, or it belongs to another store).

POST /auctions

Create an auction for one of your products. Idempotent — send an Idempotency-Key so a retry never creates a duplicate.

Scope: auctions:write

Field Type Required Notes
product_id string yes A Kernvel product id (prod_…) you own; see Products.
title string no Up to 500 chars. Defaults from the product.
description string no
auction_type string no standard (default), proxy, popcorn, penny, dutch, sealed.
visibility string no public (default), private, unlisted.
scheduled_start string (ISO-8601) no When the auction should start; omit to start now.
duration_hours int no 1–720, default 24.
starting_price number yes ≥ 0.
reserve_price number no The hidden minimum; must be ≥ starting_price.
buy_now_price number no Must be > starting_price.
min_bid_increment number no Default 1.0.
currency string no 3-letter code, default USD.
popcorn_enabled bool no Anti-snipe time extension. Default false.
payment_due_hours int no 1–168, default 48.

Unknown fields are rejected (422). Internal/cross-app fields (wishlister targeting, AI flags, liquidation provenance) are not settable via the API — drive those from the app.

curl https://auctionai.kernvellabs.com/api/public/v1/auctions -X POST \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7d8f2c1a-3b4e-4f9a-9c2d-1e2f3a4b5c6d" \
  -d '{
    "product_id": "prod_412",
    "title": "Vintage Omega Seamaster",
    "starting_price": 100,
    "reserve_price": 300,
    "buy_now_price": 650,
    "duration_hours": 96,
    "popcorn_enabled": true
  }'

Responds 201 Created with the new auction object ("status": "scheduled" or "draft" until it starts).

Notable errors: 404 not_found (product not found / not yours), 422 unprocessable (invalid parameters, e.g. reserve_price below starting_price), 409 idempotency_conflict.

PATCH /auctions/{auction_id}

Update a live or scheduled auction. Idempotent.

Scope: auctions:write

Field Type Notes
title string Up to 500 chars.
description string
visibility string public, private, unlisted.
extend_hours int 1–168 — push the end time out.
buy_now_price number Must be > 0.
popcorn_enabled bool
curl https://auctionai.kernvellabs.com/api/public/v1/auctions/auc_8123 -X PATCH \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 2a1b9c8d-7e6f-4a3b-8c2d-9f1e0a2b3c4d" \
  -d '{ "extend_hours": 12, "buy_now_price": 700 }'

Returns the updated auction object and emits auction.updated.

Notable errors: 404 not_found, 409 conflict (the auction has already ended or is otherwise in a state that can't be edited), 409 idempotency_conflict.

DELETE /auctions/{auction_id}

Cancel an auction. Idempotent. This is a state change (cancellation), not a hard delete — the auction stays readable with "status": "cancelled".

Scope: auctions:manage

Param In Type Notes
auction_id path string The auction id.
curl https://auctionai.kernvellabs.com/api/public/v1/auctions/auc_8123 -X DELETE \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx" \
  -H "Idempotency-Key: 9f8e7d6c-5b4a-4938-8271-6c5d4e3f2a1b"

Returns the cancelled auction object and emits auction.cancelled.

Notable errors: 404 not_found, 409 conflict (already ended/sold and can't be cancelled).

JavaScript (fetch)

const BASE = "https://auctionai.kernvellabs.com/api/public/v1";
const KEY = process.env.AUCTIONAI_API_KEY; // kv_auc_live_...

// List active auctions
const res = await fetch(`${BASE}/auctions?status=active&limit=25`, {
  headers: { Authorization: `Bearer ${KEY}` },
});
if (!res.ok) throw new Error(`API ${res.status}: ${(await res.json()).detail}`);
const { data, next_cursor } = await res.json();
console.log(`${data.length} active auctions`, next_cursor);

// Create an auction (idempotent)
const created = await fetch(`${BASE}/auctions`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    product_id: "prod_412",
    starting_price: 100,
    reserve_price: 300,
    duration_hours: 96,
  }),
});
console.log(await created.json());

Bids

Read an auction's bid history, or place a bid on behalf of one of your customers (handy if you run bidding from your own front-end). Bids are nested under an auction.

The bid object:

{
  "object": "bid",
  "id": "bid_55920",
  "auction_id": "auc_8123",
  "amount": "330.00",
  "currency": "USD",
  "status": "winning",
  "source": "api",
  "created_at": "2026-06-23T11:42:05Z"
}
Field Meaning
amount The bid amount (decimal string).
status pending, accepted, winning, outbid, won, rejected, retracted, fraud_flagged, or forfeited.
source Where the bid came from — api for bids you place through this API (vs web, mobile, proxy).

GET /auctions/{auction_id}/bids

List bids on an auction, newest first. Cursor-paginated.

Scope: bids:read

Param In Type Notes
auction_id path string The auction id (auc_…).
limit query int 1–100, default 25.
cursor query string The next_cursor from the previous page.
curl "https://auctionai.kernvellabs.com/api/public/v1/auctions/auc_8123/bids?limit=2" \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "bid",
      "id": "bid_55920",
      "auction_id": "auc_8123",
      "amount": "330.00",
      "currency": "USD",
      "status": "winning",
      "source": "api",
      "created_at": "2026-06-23T11:42:05Z"
    },
    {
      "object": "bid",
      "id": "bid_55918",
      "auction_id": "auc_8123",
      "amount": "320.00",
      "currency": "USD",
      "status": "outbid",
      "source": "web",
      "created_at": "2026-06-23T11:30:12Z"
    }
  ],
  "has_more": true,
  "next_cursor": "WyJudWxsIiw1NTkxOF0"
}

Notable errors: 404 not_found (no such auction, or not yours), 403 insufficient_scope.

POST /auctions/{auction_id}/bids

Place a bid on behalf of a customer. Idempotent — required on this money/state-moving write, so always send an Idempotency-Key.

Scope: bids:write

Field Type Required Notes
amount number yes Must be > 0 and meet the auction's current price + minimum increment.
customer_id string yes The Shopify customer id the bid is attributed to.
customer_email string no Used to create the bidder record if this customer is new to the app.
customer_name string no Same — for first-time bidders.

The bid is recorded with "source": "api". The customer is resolved (or created) within your store; you never pass a shop.

curl https://auctionai.kernvellabs.com/api/public/v1/auctions/auc_8123/bids -X POST \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4c3d2e1f-0a9b-4c8d-7e6f-5a4b3c2d1e0f" \
  -d '{
    "amount": 330,
    "customer_id": "gid://shopify/Customer/1234567890",
    "customer_email": "buyer@example.com",
    "customer_name": "Jordan Lee"
  }'

Responds 201 Created with the bid object and emits bid.placed.

Notable errors:

  • 409 conflict — the bid couldn't be accepted: the auction has ended, the bid is below the current price + increment, or the bidder isn't allowed (e.g. an early-access window). The detail explains which.
  • 404 not_found — no such auction, or the customer couldn't be resolved.
  • 409 idempotency_conflict — same Idempotency-Key, different body.

JavaScript (fetch)

const BASE = "https://auctionai.kernvellabs.com/api/public/v1";
const KEY = process.env.AUCTIONAI_API_KEY;

async function placeBid(auctionId, amount, customerId) {
  const res = await fetch(`${BASE}/auctions/${auctionId}/bids`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({ amount, customer_id: customerId }),
  });
  const body = await res.json();
  if (res.status === 409) {
    // e.g. outbid or auction ended — show body.detail to the shopper
    throw new Error(`Bid rejected: ${body.detail}`);
  }
  if (!res.ok) throw new Error(`API ${res.status}: ${body.detail}`);
  return body; // the bid object
}

const bid = await placeBid("auc_8123", 330, "gid://shopify/Customer/1234567890");
console.log(bid.status); // "winning"

Products

The products in your store that are available to auction (synced from Shopify). Read-only over the API — you create auctions from these products with POST /auctions.

The product object:

{
  "object": "product",
  "id": "prod_412",
  "title": "Vintage Omega Seamaster",
  "description": "1968 stainless steel, recently serviced.",
  "shopify_product_id": "8472910011223",
  "sku": "OMG-SEA-68",
  "condition": "good",
  "currency": "USD",
  "original_price": "499.00",
  "image_url": "https://cdn.shopify.com/.../seamaster.jpg",
  "created_at": "2026-06-10T14:00:00Z"
}
Field Meaning
id The Kernvel product id (prod_…) — pass this as product_id when creating an auction.
shopify_product_id The underlying Shopify product id.
condition new, like_new, good, fair, or poor.
original_price The product's reference price (decimal string), if set.
image_url The primary image.

GET /products

List your products, newest first. Cursor-paginated.

Scope: products:read

Param In Type Notes
limit query int 1–100, default 25.
cursor query string The next_cursor from the previous page.
curl "https://auctionai.kernvellabs.com/api/public/v1/products?limit=2" \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "product",
      "id": "prod_412",
      "title": "Vintage Omega Seamaster",
      "description": "1968 stainless steel, recently serviced.",
      "shopify_product_id": "8472910011223",
      "sku": "OMG-SEA-68",
      "condition": "good",
      "currency": "USD",
      "original_price": "499.00",
      "image_url": "https://cdn.shopify.com/.../seamaster.jpg",
      "created_at": "2026-06-10T14:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Notable errors: 403 insufficient_scope, 400 invalid_request (bad cursor).

GET /products/{product_id}

Fetch a single product by id.

Scope: products:read

Param In Type Notes
product_id path string The product id, e.g. prod_412.
curl https://auctionai.kernvellabs.com/api/public/v1/products/prod_412 \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx"

Returns the product object shown above.

Notable errors: 404 not_found (no such product, or it belongs to another store).

JavaScript (fetch)

const BASE = "https://auctionai.kernvellabs.com/api/public/v1";
const KEY = process.env.AUCTIONAI_API_KEY;

// Page through every product
let cursor = null, all = [];
do {
  const url = new URL(`${BASE}/products`);
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("cursor", cursor);
  const res = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } });
  const page = await res.json();
  all.push(...page.data);
  cursor = page.has_more ? page.next_cursor : null;
} while (cursor);
console.log(`${all.length} products`);

Account

Read your store's plan, status, and API capabilities — useful for showing your own usage UI or checking limits before a batch job.

GET /account

The store's plan and what its API access allows.

Scope: account:read

curl https://auctionai.kernvellabs.com/api/public/v1/account \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx"
{
  "object": "account",
  "id": "shop_91",
  "shop_domain": "your-store.myshopify.com",
  "plan": "pro",
  "subscription_status": "active",
  "currency": "USD",
  "test_mode": false,
  "api_access": {
    "live_keys": true,
    "max_keys": 25,
    "scopes": [
      "account:read", "webhooks:manage",
      "auctions:read", "auctions:write", "auctions:manage",
      "bids:read", "bids:write", "bidders:manage",
      "winners:read", "products:read", "customers:read"
    ],
    "rate_per_min": 300,
    "webhook_endpoints": 5
  }
}
Field Meaning
plan free, starter, pro, or enterprise.
subscription_status active, pending, cancelled, or expired. Live keys only work while active.
test_mode Whether the key you called with is a test key.
api_access.scopes The scopes grantable on your plan.

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

GET /account/usage

A snapshot of your auction usage and your current rate-limit window.

Scope: account:read

curl https://auctionai.kernvellabs.com/api/public/v1/account/usage \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx"
{
  "object": "usage",
  "plan": "pro",
  "active_auctions": 37,
  "auction_limit": 100,
  "rate_limit": 300,
  "rate_limit_remaining": 298
}
Field Meaning
active_auctions / auction_limit How many active auctions you have vs your plan's cap.
rate_limit / rate_limit_remaining Your per-minute limit and what's left in this window (also in the X-RateLimit-* headers).

JavaScript (fetch)

const BASE = "https://auctionai.kernvellabs.com/api/public/v1";
const KEY = process.env.AUCTIONAI_API_KEY;

const res = await fetch(`${BASE}/account/usage`, {
  headers: { Authorization: `Bearer ${KEY}` },
});
const usage = await res.json();
if (usage.active_auctions >= usage.auction_limit) {
  console.warn("At your auction limit — upgrade before creating more.");
}

Webhooks

Manage the endpoints we POST events to (so you don't have to poll). Available on Pro and Enterprise plans. See Webhook events for the event catalog and how to verify signatures.

The subscription object:

{
  "object": "webhook_subscription",
  "id": "whk_7",
  "url": "https://yourapp.com/hooks/auctionai",
  "events": ["auction.created", "bid.placed"],
  "description": "Prod listener",
  "env": "live",
  "is_active": true,
  "created_at": "2026-06-23T10:00:00Z"
}

The signing secret is only returned once, on create — it's never shown again in list/get responses.

POST /webhooks

Create a subscription. Returns the signing secret exactly once.

New subscriptions return 201. 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 Notes
url string yes Must be https and resolve to a public IP (private/loopback/metadata addresses are rejected).
events string[] yes At least one event from the catalog. Unknown events are rejected.
description string no Up to 255 chars.
curl https://auctionai.kernvellabs.com/api/public/v1/webhooks -X POST \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/hooks/auctionai",
    "events": ["auction.created", "bid.placed"],
    "description": "Prod listener"
  }'
{
  "object": "webhook_subscription",
  "id": "whk_7",
  "url": "https://yourapp.com/hooks/auctionai",
  "events": ["auction.created", "bid.placed"],
  "description": "Prod listener",
  "env": "live",
  "is_active": true,
  "created_at": "2026-06-23T10:00:00Z",
  "signing_secret": "whsec_Hk3...redacted...9aZ"
}

The subscription's env matches the key you created it with — a live key makes a live subscription (receiving live events), a test key makes a test subscription.

Notable errors:

  • 400 invalid_request — the URL isn't https or resolves to a private/reserved IP.
  • 422 invalid_request — events contains an unknown event type.
  • 403 feature_not_in_plan — webhooks aren't in your plan, or you've hit your plan's endpoint cap (5 on Pro, 20 on Enterprise).

GET /webhooks

List your subscriptions (no secrets).

Scope: webhooks:manage

curl https://auctionai.kernvellabs.com/api/public/v1/webhooks \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx"
{
  "object": "list",
  "data": [
    {
      "object": "webhook_subscription",
      "id": "whk_7",
      "url": "https://yourapp.com/hooks/auctionai",
      "events": ["auction.created", "bid.placed"],
      "description": "Prod listener",
      "env": "live",
      "is_active": true,
      "created_at": "2026-06-23T10:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET /webhooks/{webhook_id}

Fetch one subscription (no secret).

Scope: webhooks:manage

curl https://auctionai.kernvellabs.com/api/public/v1/webhooks/whk_7 \
  -H "Authorization: Bearer kv_auc_live_xxxxxxxx"

Returns the subscription object (without signing_secret).

Notable errors: 404 not_found.

DELETE /webhooks/{webhook_id}

Delete a subscription. We stop sending it events immediately.

Scope: webhooks:manage

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

Notable errors: 404 not_found.

JavaScript (fetch)

const BASE = "https://auctionai.kernvellabs.com/api/public/v1";
const KEY = process.env.AUCTIONAI_API_KEY;

const res = await fetch(`${BASE}/webhooks`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://yourapp.com/hooks/auctionai",
    events: ["auction.created", "bid.placed"],
  }),
});
const sub = await res.json();
// Store sub.signing_secret NOW — it is never returned again.
console.log("Save this secret:", sub.signing_secret);

Webhook events

Subscribe to any of these on a webhook subscription. Each delivery is the standard envelope — id, type, api_version, created_at, and a data object carrying the relevant resource.

Event Fires when data
auction.created An auction is created (including via POST /auctions). auction object
auction.updated An auction is edited. auction object
auction.cancelled An auction is cancelled. auction object
auction.started A scheduled auction goes live (its start time is reached). auction object
auction.ended An auction reaches its end — fires for every close, whether it sold, got no bids, or missed reserve. auction object
auction.sold An auction ends with a winning bid above reserve (a sale was made). auction object
auction.won An auction is won — carries the winning bidder and final price. auction object
bid.placed A bid is accepted (including via the API). bid object
bid.outbid A previous high bidder is displaced by a higher bid. Fires once per displaced bidder, carrying their now-outbid bid. bid object
payment.captured A won auction's payment is captured (the order is paid / marked sold). auction object

These are the event types you can subscribe to today. Creation/edit/cancel and bid.placed are emitted on the public API write path; the lifecycle events (auction.started / auction.ended / auction.sold / auction.won / bid.outbid / payment.captured) are emitted server-side as auctions run, so you get notified without polling.

Delivery envelope

{
  "id": "evt_3f1c9a2b7d4e6f80a1b2c3d4",
  "type": "auction.created",
  "api_version": "2026-06-23",
  "created_at": "2026-06-24T09:00:01Z",
  "data": {
    "object": "auction",
    "id": "auc_8123",
    "title": "Vintage Omega Seamaster",
    "status": "scheduled",
    "auction_type": "standard",
    "currency": "USD",
    "current_price": "330.00",
    "starting_price": "100.00",
    "reserve_price": "300.00",
    "buy_now_price": "650.00",
    "final_price": null,
    "total_bids": 15,
    "unique_bidders": 7,
    "product_id": "prod_412",
    "starts_at": "2026-06-20T09:00:00Z",
    "ends_at": "2026-06-24T09:00:00Z",
    "ended_at": null,
    "created_at": "2026-06-19T18:30:00Z"
  }
}

The bid events (bid.placed, bid.outbid) carry a bid object in data instead; every other event carries the auction object shown above.

Delivery headers

Every delivery carries:

Content-Type: application/json
X-Kernvel-Signature: t=1750671601,v1=8f3c...hex...
X-Kernvel-Event-Id: evt_3f1c9a2b7d4e6f80a1b2c3d4
X-Kernvel-Event-Type: auction.created
User-Agent: Kernvel-Webhooks/1.0

Verify the signature

The X-Kernvel-Signature header is t=<unix>,v1=<hex>, where v1 is HMAC-SHA256(signing_secret, "<t>.<raw-body>"). Verify it against the raw request body (before JSON parsing) and reject anything older than 5 minutes — that stops a captured payload from being replayed.

import crypto from "node:crypto";

function verifyKernvelWebhook(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((kv) => kv.split("="))
  );
  const t = parseInt(parts.t, 10);
  if (Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 min

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  // constant-time compare
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express example — note express.raw() so rawBody is the unparsed bytes
app.post("/hooks/auctionai", express.raw({ type: "application/json" }), (req, res) => {
  const ok = verifyKernvelWebhook(
    req.body.toString("utf8"),
    req.headers["x-kernvel-signature"],
    process.env.AUCTIONAI_WEBHOOK_SECRET
  );
  if (!ok) return res.status(400).send("bad signature");

  const event = JSON.parse(req.body.toString("utf8"));
  // handle event.type / event.data ...
  res.sendStatus(200); // 2xx = delivered; anything else triggers a retry
});

Reply with any 2xx to acknowledge. Non-2xx (or a timeout) is retried with backoff (1m, 5m, 30m, 2h, 6h); after 15 consecutive failures we disable the endpoint and email you.