Products
A product is the catalog entry buyers see. Each product has one or more variants (size, color, SKU-level differences) and every stock row + shipment line + license pin to a variant, not the product. The Node SDK exposes both layers behind the fulkruma.products namespace. For the underlying HTTP surface and the full field tables, see API: Products.
Namespace
fulkruma.products — every method:
fulkruma.products.create(input)
fulkruma.products.get(id)
fulkruma.products.list(params?)
fulkruma.products.update(id, patch)
fulkruma.products.archive(id)
fulkruma.products.addVariant(productId, input)
fulkruma.products.updateVariant(productId, variantId, patch)
fulkruma.products.archiveVariant(productId, variantId)
Eight methods — four for products, three for variants, plus archive. There's no top-level variants namespace; variants are always addressed via their parent product so the URL space stays nested.
Methods
products.create
Signature. fulkruma.products.create(input): Promise<{ product: Product }>
Creates a product. Only name is required. type defaults to physical; pass digital for download-only goods (which then unlock the deliveries + licenses surfaces) or license for software sold as a key. The SDK auto-mints an Idempotency-Key, so retries against transient network errors are safe.
const { product } = await fulkruma.products.create({
name: 'Pawpado Premium License',
type: 'digital',
description: 'GPU portal — 12-month subscription',
licenseEnabled: true,
maxActivations: 3,
sku: 'pawpado-premium-12m',
});
sku is your handle into your own catalog. Products that Storlaunch mirrors into Fulkruma also carry externalRef + externalSource ("storlaunch"); those are set by that sync only — create and update don't accept them.
products.get
Signature. fulkruma.products.get(id): Promise<{ product: Product }>
Fetches one product + every variant in a single response. Returns 404 not_found for missing or cross-workspace IDs.
const { product } = await fulkruma.products.get('prod_01HX...');
console.log(product.variants.length);
products.list
Signature. fulkruma.products.list(params?): Promise<{ products: Product[] }>
Returns every product in the workspace. Pass archived: true to include archived ones, archived: false (or omit) to exclude. Not paginated — we expect catalogs in the hundreds, not millions.
const { products } = await fulkruma.products.list({ archived: false });
for (const p of products) {
console.log(p.name, p.variants.length, 'variants');
}
products.update
Signature. fulkruma.products.update(id, patch): Promise<{ product: Product }>
PATCH semantics. To toggle license-mode on an existing product, pass licenseEnabled: true; the next licenses.issue call against that product will succeed instead of returning 400.
await fulkruma.products.update('prod_01HX...', {
description: 'Updated copy',
licenseEnabled: true,
});
products.archive
Signature. fulkruma.products.archive(id): Promise<{ archived: boolean }>
Soft-delete. Variants archive alongside the parent automatically. Historic shipments/licenses/stock movements still reference the archived product so audit trails keep working.
await fulkruma.products.archive('prod_01HX...');
products.addVariant
Signature. fulkruma.products.addVariant(productId, input): Promise<{ variant: ProductVariant }>
Adds a variant. name is required (e.g. 'Size L / Red'); SKU, prices, low-stock threshold, and weight are optional. Set isDefault: true to make this the variant that pre-selects in checkout flows.
const { variant } = await fulkruma.products.addVariant('prod_01HX...', {
name: 'Size L',
sku: 'TSHIRT-L',
priceCents: 25_000_00, // IDR 25,000
costCents: 12_500_00,
lowStockThreshold: 5,
isDefault: true,
});
Variants share the parent product's type — you can't mix physical + digital variants under one product.
products.updateVariant / products.archiveVariant
await fulkruma.products.updateVariant('prod_01HX...', 'var_01HX...', {
priceCents: 30_000_00,
});
await fulkruma.products.archiveVariant('prod_01HX...', 'var_01HX...');
Archiving the last live variant on a product is allowed but unusual — you typically archive the whole product instead.
Types
type ProductType = 'physical' | 'digital' | 'license';
interface Product {
id: string; // 'prod_...'
accountId: string;
name: string;
sku: string | null;
description: string | null;
type: ProductType;
weight: number | null;
length: number | null;
width: number | null;
height: number | null;
licenseEnabled: boolean;
maxActivations: number | null;
externalRef: string | null;
externalSource: string | null;
variants: ProductVariant[];
archivedAt: string | null;
createdAt: string;
updatedAt: string;
}
interface ProductVariant {
id: string; // 'var_...'
productId: string;
name: string;
sku: string | null;
priceCents: number | null;
costCents: number | null;
lowStockThreshold: number | null;
weight: number | null;
isDefault: boolean;
externalRef: string | null;
externalSource: string | null;
archivedAt: string | null;
}
For per-field validation rules and the full server-only field list (e.g. arn), see API: Products.
Common patterns
Create a product with one default variant
Most products only have one SKU. Create the product, then the variant, in two calls:
async function createSimple(name: string, sku: string, priceCents: number) {
const { product } = await fulkruma.products.create({ name });
await fulkruma.products.addVariant(product.id, {
name: 'Default',
sku,
priceCents,
isDefault: true,
});
return product.id;
}
Sync from your own catalog
If your own system owns the source-of-truth catalog, use sku to dedupe:
async function upsertProduct(sku: string, attrs: { name: string; priceCents: number }) {
const { products } = await fulkruma.products.list();
const existing = products.find((p) => p.sku === sku);
if (existing) {
await fulkruma.products.update(existing.id, { name: attrs.name });
return existing.id;
}
const { product } = await fulkruma.products.create({
name: attrs.name,
sku,
});
await fulkruma.products.addVariant(product.id, {
name: 'Default',
priceCents: attrs.priceCents,
isDefault: true,
});
return product.id;
}
Errors
| Code | Status | Cause |
|---|---|---|
VALIDATION |
400 | Missing name, bad type, a negative dimension or price. |
NO_ACCOUNT |
403 | The credentials resolve to no workspace. |
NOT_FOUND |
404 | Product or variant ID missing or in another workspace. |
Next
- Stock — per-variant per-warehouse inventory.
- Licenses — digital-product license keys.
- API: Products — HTTP reference.