API ReferenceEndpointsInventory

Inventory

Inventory tracks stock per product/variant: total, reserved (held by active checkouts), and available (total - reserved). Use these endpoints to adjust stock manually, run bulk lookups, check availability, and reconcile drift between Brainerce and external platforms.

Get inventory

Returns stock levels for the active products published to this channel, with a per-variant breakdown for variable products. Products that are drafts, archived or not published to this channel are never returned, even when named in productIds.

GET
/vc/{connectionId}/inventory
X-Sales-Channel-Origin<token>

For /api/vc/{connectionId}/* routes the connectionId (vc_*) is in the URL path — not a header. Live-mode requests must also send a matching Origin header.

In: header

Path Parameters

connectionIdstring

Sales channel ID (vc_*), the canonical salesChannelId. connectionId is the deprecated name of the same value and is still the literal path segment.

Query Parameters

productIds?string

Comma-separated product ids to narrow the result. Omitted: every active product published to this channel.

Header Parameters

originstring

Origin of the storefront page making the call, for example https://shop.example.com. Required: a request with no Origin header is refused with 403. On a LIVE channel its host must equal the channel's registered domain (or one of its subdomains) or match an allowed origin. A TEST channel with no domain accepts any origin; once a domain is set it is matched as on LIVE.

X-SDK-Version?string

Version of the calling SDK (the Brainerce SDK sends it automatically). Informational: it is recorded on the channel and never changes the response.

curl -X GET "https://api.brainerce.com/api/vc/string/inventory?productIds=clprod_a%2Cclprod_b" \  -H "origin: string" \  -H "X-SDK-Version: string"
{
  "inventory": [
    {
      "productId": "clprd_abc123",
      "sku": "MUG-BLU",
      "name": "Blue mug",
      "type": "SIMPLE",
      "inventory": {
        "total": 42,
        "reserved": 3,
        "available": 39,
        "trackingMode": "TRACKED",
        "inStock": true,
        "canPurchase": true
      },
      "variants": [
        {
          "variantId": "clvar_abc123",
          "sku": "MUG-BLU-L",
          "name": "Blue / Large",
          "inventory": {
            "total": 42,
            "reserved": 3,
            "available": 39,
            "trackingMode": "TRACKED",
            "inStock": true,
            "canPurchase": true
          }
        }
      ]
    }
  ]
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/inventory"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/inventory"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/inventory"
}

Check stock availability

Pre-flight stock check for up to 50 lines, using the same channel-aware numbers the cart and checkout enforce, so an "OK" here is not refused one step later. Nothing is reserved. Needs inventory:read or products:read.

POST
/vc/{connectionId}/inventory/check-availability
X-Sales-Channel-Origin<token>

For /api/vc/{connectionId}/* routes the connectionId (vc_*) is in the URL path — not a header. Live-mode requests must also send a matching Origin header.

In: header

Path Parameters

connectionIdstring

Sales channel ID (vc_*), the canonical salesChannelId. connectionId is the deprecated name of the same value and is still the literal path segment.

Header Parameters

originstring

Origin of the storefront page making the call, for example https://shop.example.com. Required: a request with no Origin header is refused with 403. On a LIVE channel its host must equal the channel's registered domain (or one of its subdomains) or match an allowed origin. A TEST channel with no domain accepts any origin; once a domain is set it is matched as on LIVE.

X-SDK-Version?string

Version of the calling SDK (the Brainerce SDK sends it automatically). Informational: it is recorded on the channel and never changes the response.

itemsarray<VcCartItemInputDto>

Line items. At most 50 per request.

Items1 <= items <= 50
curl -X POST "https://api.brainerce.com/api/vc/string/inventory/check-availability" \  -H "origin: string" \  -H "X-SDK-Version: string" \  -H "Content-Type: application/json" \  -d '{    "items": [      {        "productId": "clx1234567890",        "quantity": 2      }    ]  }'
{
  "allAvailable": true,
  "results": [
    {
      "productId": "clprd_abc123",
      "variantId": "clvar_abc123",
      "isAvailable": true,
      "available": 12,
      "requested": 2,
      "shortfall": 0,
      "trackingMode": "TRACKED"
    }
  ]
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/inventory/check-availability"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/inventory/check-availability"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/inventory/check-availability"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/inventory/check-availability"
}

Get availability

Returns per-product stock figures accounting for active reservations. Cap of 50 productIds per request.

POST
/vc/{connectionId}/availability
X-Sales-Channel-Origin<token>

For /api/vc/{connectionId}/* routes the connectionId (vc_*) is in the URL path — not a header. Live-mode requests must also send a matching Origin header.

In: header

Path Parameters

connectionIdstring

Sales channel ID (vc_*), the canonical salesChannelId. connectionId is the deprecated name of the same value and is still the literal path segment.

Header Parameters

originstring

Origin of the storefront page making the call, for example https://shop.example.com. Required: a request with no Origin header is refused with 403. On a LIVE channel its host must equal the channel's registered domain (or one of its subdomains) or match an allowed origin. A TEST channel with no domain accepts any origin; once a domain is set it is matched as on LIVE.

X-SDK-Version?string

Version of the calling SDK (the Brainerce SDK sends it automatically). Informational: it is recorded on the channel and never changes the response.

productIdsarray<string>

Products to report availability for.

Itemsitems <= 200
curl -X POST "https://api.brainerce.com/api/vc/string/availability" \  -H "origin: string" \  -H "X-SDK-Version: string" \  -H "Content-Type: application/json" \  -d '{    "productIds": [      "string"    ]  }'
[
  {
    "productId": "string",
    "variantId": "string",
    "trackingMode": "TRACKED",
    "total": 0,
    "reserved": 0,
    "available": 0,
    "canPurchase": true,
    "lowStock": true
  }
]

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/availability"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/availability"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/availability"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/availability"
}

Extend reservation

Extends the expiry of an active reservation for a cart or checkout. Returns 400 if the sales-channel reservation strategy is ON_PAYMENT.

POST
/vc/{connectionId}/reservation/extend
X-Sales-Channel-Origin<token>

For /api/vc/{connectionId}/* routes the connectionId (vc_*) is in the URL path — not a header. Live-mode requests must also send a matching Origin header.

In: header

Path Parameters

connectionIdstring

Sales channel ID (vc_*), the canonical salesChannelId. connectionId is the deprecated name of the same value and is still the literal path segment.

Query Parameters

cartToken?string

Proof that the caller holds this cart. For a guest cart send the cart sessionToken (returned when the cart was created). Required on every cart write when the store runs cart ownership in enforce mode; a missing or wrong token answers 403 CART_ACCESS_DENIED (never 401). A customer cart is proven by the customer Bearer token instead. Send either the cartToken query parameter or the X-Cart-Session header.

Header Parameters

originstring

Origin of the storefront page making the call, for example https://shop.example.com. Required: a request with no Origin header is refused with 403. On a LIVE channel its host must equal the channel's registered domain (or one of its subdomains) or match an allowed origin. A TEST channel with no domain accepts any origin; once a domain is set it is matched as on LIVE.

X-SDK-Version?string

Version of the calling SDK (the Brainerce SDK sends it automatically). Informational: it is recorded on the channel and never changes the response.

x-cart-session?string

Proof that the caller holds this cart. For a guest cart send the cart sessionToken (returned when the cart was created). Required on every cart write when the store runs cart ownership in enforce mode; a missing or wrong token answers 403 CART_ACCESS_DENIED (never 401). A customer cart is proven by the customer Bearer token instead. Send either the cartToken query parameter or the X-Cart-Session header.

cartId?string

Cart whose stock reservation this affects.

Lengthlength <= 100
checkoutId?string

Checkout whose stock reservation this affects.

Lengthlength <= 100
curl -X POST "https://api.brainerce.com/api/vc/string/reservation/extend?cartToken=string" \  -H "origin: string" \  -H "X-SDK-Version: string" \  -H "x-cart-session: string" \  -H "Content-Type: application/json" \  -d '{}'
{
  "success": true,
  "reservation": {
    "hasReservation": true,
    "expiresAt": "2019-08-24T14:15:22Z",
    "remainingSeconds": 0,
    "strategy": "ON_CHECKOUT",
    "countdownMessage": "string"
  }
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/reservation/extend"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/reservation/extend"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/reservation/extend"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/reservation/extend"
}

Release reservation

Releases an active reservation for a cart or checkout (e.g. shopper abandoned the flow). No-op if no active reservations are found. Returns 400 if the strategy is ON_PAYMENT.

POST
/vc/{connectionId}/reservation/release
X-Sales-Channel-Origin<token>

For /api/vc/{connectionId}/* routes the connectionId (vc_*) is in the URL path — not a header. Live-mode requests must also send a matching Origin header.

In: header

Path Parameters

connectionIdstring

Sales channel ID (vc_*), the canonical salesChannelId. connectionId is the deprecated name of the same value and is still the literal path segment.

Query Parameters

cartToken?string

Proof that the caller holds this cart. For a guest cart send the cart sessionToken (returned when the cart was created). Required on every cart write when the store runs cart ownership in enforce mode; a missing or wrong token answers 403 CART_ACCESS_DENIED (never 401). A customer cart is proven by the customer Bearer token instead. Send either the cartToken query parameter or the X-Cart-Session header.

Header Parameters

originstring

Origin of the storefront page making the call, for example https://shop.example.com. Required: a request with no Origin header is refused with 403. On a LIVE channel its host must equal the channel's registered domain (or one of its subdomains) or match an allowed origin. A TEST channel with no domain accepts any origin; once a domain is set it is matched as on LIVE.

X-SDK-Version?string

Version of the calling SDK (the Brainerce SDK sends it automatically). Informational: it is recorded on the channel and never changes the response.

x-cart-session?string

Proof that the caller holds this cart. For a guest cart send the cart sessionToken (returned when the cart was created). Required on every cart write when the store runs cart ownership in enforce mode; a missing or wrong token answers 403 CART_ACCESS_DENIED (never 401). A customer cart is proven by the customer Bearer token instead. Send either the cartToken query parameter or the X-Cart-Session header.

cartId?string

Cart whose stock reservation this affects.

Lengthlength <= 100
checkoutId?string

Checkout whose stock reservation this affects.

Lengthlength <= 100
curl -X POST "https://api.brainerce.com/api/vc/string/reservation/release?cartToken=string" \  -H "origin: string" \  -H "X-SDK-Version: string" \  -H "x-cart-session: string" \  -H "Content-Type: application/json" \  -d '{}'
{
  "success": true
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/reservation/release"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/reservation/release"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/reservation/release"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/reservation/release"
}

Request a back-in-stock alert

Records a "notify me when this is back" request for an out-of-stock product or variant. Nothing is mailed at request time — exactly one email goes out later, once the item is genuinely available again on this sales channel and has stayed that way. Pass variantId for a variable product: an alert on the blue medium fires only for the blue medium, never when some other size returns.

Unlike POST /marketing/subscribe this needs no confirmation round trip, because it grants no marketing consent and creates no customer account. It is not a newsletter signup and must not be presented as one.

The response is identical for a new request, a duplicate, an unknown product and an item that is already in stock, so it cannot be used to read this store's stock levels or customer list — do not branch on it to tell the shopper whether the item is in stock.

No authentication. Include the honeypot field as a hidden, empty input. Rate limited to 5 requests per minute rather than the inquiry route's 3, because a shopper legitimately asks about several sizes in a row and no mail leaves at request time anyway.

POST
/vc/{connectionId}/stock-alerts
X-Sales-Channel-Origin<token>

For /api/vc/{connectionId}/* routes the connectionId (vc_*) is in the URL path — not a header. Live-mode requests must also send a matching Origin header.

In: header

Path Parameters

connectionIdstring

Sales channel ID (vc_*), the canonical salesChannelId. connectionId is the deprecated name of the same value and is still the literal path segment.

Header Parameters

originstring

Origin of the storefront page making the call, for example https://shop.example.com. Required: a request with no Origin header is refused with 403. On a LIVE channel its host must equal the channel's registered domain (or one of its subdomains) or match an allowed origin. A TEST channel with no domain accepts any origin; once a domain is set it is matched as on LIVE.

X-SDK-Version?string

Version of the calling SDK (the Brainerce SDK sends it automatically). Informational: it is recorded on the channel and never changes the response.

emailstring

Address to notify. Lowercased and trimmed server-side. Receives exactly one email, when the item is back.

Lengthlength <= 254
productIdstring

Product the shopper is waiting for.

Length1 <= length <= 64
variantId?string

Specific variant, when the product has them. Omit for a simple product. An alert on the blue medium fires only when the blue medium is back, never when some other size returns.

Length1 <= length <= 64
locale?string

Storefront locale at submission time (e.g. "he"). Sets the language of the alert email. Falls back to the store language.

Lengthlength <= 10
honeypot?string

Anti-bot honeypot. Render it hidden and leave it empty; a filled value rejects the request. Bots complete every text input, humans never see this one.

curl -X POST "https://api.brainerce.com/api/vc/string/stock-alerts" \  -H "origin: string" \  -H "X-SDK-Version: string" \  -H "Content-Type: application/json" \  -d '{    "email": "[email protected]",    "productId": "clx1a2b3c4d5e6f7g8h9"  }'
{
  "ok": true
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/stock-alerts"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/stock-alerts"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/stock-alerts"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/vc/{connectionId}/stock-alerts"
}

Get inventory for a product

Returns the full inventory state of the product. A product with no inventory row, or an ID that is not in this store, reads back as zero counters instead of answering 404.

GET
/v1/products/{id}/inventory
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

idstring

Product ID

curl -X GET "https://api.brainerce.com/api/v1/products/string/inventory"
{
  "id": "clx1a2b3c4d5e6f7g8h9",
  "productId": "clx1a2b3c4d5e6f7g8h9",
  "trackingMode": "TRACKED",
  "backorderMode": "NONE",
  "backorderLimit": 10,
  "total": 100,
  "reserved": 4,
  "available": 96,
  "lowStockThreshold": 5,
  "lastInventorySyncAt": "2019-08-24T14:15:22Z",
  "updatedAt": "2019-08-24T14:15:22Z"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/products/{id}/inventory"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/products/{id}/inventory"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Update inventory for a product

Supports Idempotency-Key header for safe retries. ⛔ Rejects a KIT with 400: a kit holds no stock of its own — its availability is MIN(floor(component.available / quantity)), recomputed from its components on every read. Set the stock on the component products instead.

PUT
/v1/products/{id}/inventory
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

idstring

Product ID

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
quantitynumber

New absolute stock level

variantId?string

Variant ID (optional). When present the variant of a VARIABLE product is set instead of the product, and the response is the inventory snapshot of that variant (trackingMode, total, reserved, available, inStock, canPurchase).

curl -X PUT "https://api.brainerce.com/api/v1/products/string/inventory" \  -H "Idempotency-Key: string" \  -H "Content-Type: application/json" \  -d '{    "quantity": 0  }'
{
  "id": "clx1a2b3c4d5e6f7g8h9",
  "productId": "clx1a2b3c4d5e6f7g8h9",
  "trackingMode": "TRACKED",
  "backorderMode": "NONE",
  "backorderLimit": 10,
  "total": 100,
  "reserved": 4,
  "available": 96,
  "lowStockThreshold": 5,
  "lastInventorySyncAt": "2019-08-24T14:15:22Z",
  "updatedAt": "2019-08-24T14:15:22Z"
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/products/{id}/inventory"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/products/{id}/inventory"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/products/{id}/inventory"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/products/{id}/inventory"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}