Inventory Manager API · products

Add a variant to a product, under the same rules as creation

POST /products/{productId}/variants

Appends inventory.variant-added. Idempotent on the SKU: a retried add meets 409 VariantSkuConflict, never a second variant.

Access

Requires products:write.

How scopes come from roles: Scopes and roles. Getting a token: Authentication and app tokens.

Retries

Idempotent on the SKU: a retried add meets 409 VariantSkuConflict, never a second variant.

A retry with the same key answers 201, with what the first call recorded.

Why every write carries a key: Idempotency and retries.

Facts appended

  • inventory.variant-added

What a fact is: Facts and the journal.

AI agent tool

Agents call this as catalog_add_variant (Catalog: Add variant), in the catalog toolset.

Connecting an agent: AI agents.

Example

Add the green widget; WIDGET-GREEN is already a stocked item.

curl
curl -X POST 'https://inventory.getmanifold.io/products/prd_019cd54b-2e40-7921-ac50-698c7db3cbeb/variants' \
  -H "Authorization: Bearer $MANIFOLD_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
  "sku": "WIDGET-GREEN",
  "options": [
    {
      "name": "Colour",
      "value": "Green"
    }
  ],
  "kind": {
    "_tag": "stocked"
  }
}'
JavaScript (fetch)
const response = await fetch("https://inventory.getmanifold.io/products/prd_019cd54b-2e40-7921-ac50-698c7db3cbeb/variants", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.MANIFOLD_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "sku": "WIDGET-GREEN",
    "options": [
      {
        "name": "Colour",
        "value": "Green"
      }
    ],
    "kind": {
      "_tag": "stocked"
    }
  }),
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const answer = await response.json();
Request body · application/json
{
  "sku": "WIDGET-GREEN",
  "options": [
    {
      "name": "Colour",
      "value": "Green"
    }
  ],
  "kind": {
    "_tag": "stocked"
  }
}
Response · 201 · application/json
{
  "id": "prd_019cd54b-2e40-7921-ac50-698c7db3cbeb",
  "title": "Widget",
  "description": "A moulded widget for everyday fixing jobs. Dishwasher safe.",
  "status": "active",
  "handle": "widget",
  "vendor": "Tidewater Supply Co.",
  "productType": "Widgets",
  "tags": [
    "Hardware",
    "Bestseller"
  ],
  "seo": {
    "title": "Widget — moulded, dishwasher safe"
  },
  "taxClass": "standard",
  "options": [
    {
      "name": "Colour",
      "values": [
        "Blue",
        "Red",
        "Green"
      ]
    }
  ],
  "variants": [
    {
      "id": "var_019cd54b-2e4a-7ee1-b04a-3bb14b60736f",
      "sku": "WIDGET-BLUE",
      "options": [
        {
          "name": "Colour",
          "value": "Blue"
        }
      ],
      "kind": {
        "_tag": "stocked"
      },
      "createdAt": "2026-03-10T01:10:00.000Z",
      "updatedAt": "2026-03-10T01:10:00.000Z"
    },
    {
      "id": "var_019cd54b-2e54-72b7-ae20-2d04347601b0",
      "sku": "WIDGET-RED",
      "options": [
        {
          "name": "Colour",
          "value": "Red"
        }
      ],
      "kind": {
        "_tag": "stocked"
      },
      "createdAt": "2026-03-10T01:10:00.000Z",
      "updatedAt": "2026-03-10T01:10:00.000Z"
    },
    {
      "id": "var_01a15c72-67e0-79db-818b-f233786beac6",
      "sku": "WIDGET-GREEN",
      "options": [
        {
          "name": "Colour",
          "value": "Green"
        }
      ],
      "kind": {
        "_tag": "stocked"
      },
      "createdAt": "2026-10-21T03:12:44.000Z",
      "updatedAt": "2026-10-21T03:12:44.000Z"
    }
  ],
  "createdAt": "2026-03-10T01:10:00.000Z",
  "updatedAt": "2026-10-21T03:12:44.000Z"
}

The panel writes this call in other languages. It does not send it: Manifold's APIs do not take calls from a web page on another site, so run the call from your own code or terminal. The whole API as OpenAPI: inventory-manager.openapi.json. Base URL https://inventory.getmanifold.io.

Errors

Every refusal is a status and a JSON body whose _tag names it. A refused call changes nothing. Some can answer a retry of a call that already worked: they are marked.

401

RefusalWhat it meansWhat to do
Unauthorized reason The token is missing, expired or not one Manifold issued.Get a fresh token and send the call again.
TokenAlreadyUsed tokenId A single-use app token was already spent.Mint a new token for this call.

402

RefusalWhat it meansWhat to do
SubscriptionRequired status The organization's subscription has lapsed, so writes are refused.An owner settles billing. Reads still work meanwhile.

403

RefusalWhat it meansWhat to do
TenantRequired userId The token names no organization.Choose the organization, then get a new token.
ScopeRequired required The token's roles do not grant the scope this call needs.Ask an owner or admin for a role that grants it.

404

RefusalWhat it meansWhat to do
ProductNotFound productId No such record in this organization.Check the id in the path or the body.

409

RefusalWhat it meansWhat to do
ProductArchived productId The product is archived, and an archived product takes no changes.Use a product that is not archived.
VariantSkuConflict sku, productId Can answer a retryThe SKU already belongs to another product's variant.If this answers a retry, your first call probably landed. Use another SKU.
SkuNotStocked sku The SKU is not stocked: it is a service or a kit, which holds no units.Use a stocked SKU.
CompositeSkuIsStocked sku A stocked SKU cannot become a kit.Use a new SKU for the kit.
SkuNotAllowed sku, alphabet, allowed The SKU has characters this organization's SKU alphabet does not allow. allowed lists them.Change the SKU, or an owner widens the alphabet in Settings › General.
VariantOptionsInvalid productId, sku, reason The variant's options do not fit the product's options. reason says how.Send an option value for each of the product's options.
BarcodeConflict barcode, sku Another SKU already has this barcode. sku names it.Use another barcode, or take it off the other SKU first.
MirroredFromSource source This area is fed by your source system until the organization goes live.Make the change in the source system, or go live first.

503

RefusalWhat it meansWhat to do
RequestLogUnavailable tokenId, reason The service could not record the request, so it did nothing.Send the same request again, with the same key.

How refusals work everywhere: Errors.