Products
The products endpoints let you read your catalog and write to it. Reads need the products:read scope and writes need products:write. Products are identified by SKU throughout.
List products
Returns a page of products, newest updatedAt first (ties broken by id). Each product includes its categoryIds and destinations. Variants aren't included in the list; read a single product to get them.
| Parameter | Type | Meaning |
|---|---|---|
| page | integer | Page number for simple paging. Defaults to 1. |
| pageSize | integer | Products per page. Defaults to 50, maximum 200. |
| after | string | Cursor from the previous page's nextCursor. When set, page is ignored. |
| updatedSince | ISO 8601 time | Only products changed at or after this time. Inclusive. |
| search | string | Matches product name or SKU, ignoring case. |
| type | string | physical, digital or service. |
| visibility | string | visible or hidden. |
The example is shortened. Every product carries the full set of fields listed in the data schema.
An updatedSince value that isn't a valid ISO 8601 time returns 400 validation_error.
Page with a cursor
For large catalogs, page with the cursor rather than page. Pass nextCursor back as after and keep your other filters the same:
In cursor mode, page is left out of pagination. nextCursor is set whenever a page comes back full, so you may get one final empty page with nextCursor: null. Stop when nextCursor is null.
Keep a copy in sync
To mirror your catalog in another system without re-reading everything, poll with updatedSince and a stored watermark:
- Store a watermark: the newest
updatedAtyou've processed. - Call
GET /products?updatedSince=<watermark>&pageSize=200. - Process
data. Whilepagination.nextCursoris set, fetch the next page with&after=<nextCursor>, keepingupdatedSincethe same. - When
nextCursorisnull, set the watermark to the newestupdatedAtyou processed.
updatedSince is inclusive, so reusing the exact watermark is safe. You may see the boundary product again, so de-duplicate by id or sku.
Get a product by SKU
Returns one product with its categoryIds, destinations and variants. The SKU is matched ignoring case and surrounding spaces. URL-encode it.
A variant price of null means the variant uses the product's base price. A SKU that doesn't exist in your organization returns 404 not_found.
Create or update products
Send one product object, or a products array of up to 500. Each product is matched by sku: if the SKU exists it's updated, and if not it's created.
What a create needs
Creating a product needs sku, name and basePrice. type defaults to physical. A new product starts with its Sync switch and Channel Sync off and is hidden (isVisible: false) until you change that. See Sync control for switching it on through the API.
Partial updates
On an update, send only what changes:
Send null to clear a field:
- Scalar fields change only when you send them.
imagesandcategoriesreplace the whole list when sent.imagestakes an array of{ "url": "…", "alt": "…" }objects or plain URL strings. Image URLs must be publichttpsaddresses.descriptionis HTML. It's cleaned to the same set of tags and attributes the product editor allows.variantsare matched by variant SKU and updated in place. A new variant SKU sent withoptionsis created. See Variants.
The full list of writable fields is in the data schema.
Brands, categories and linked records
brandtakes a brand name or id. Names are matched ignoring case.nullclears the brand.categoriestakes an array of category names or ids, and replaces the product's categories.- A brand or category that doesn't exist fails that product, and its name is listed in
summary.unresolvedBrandsorsummary.unresolvedCategories. Create it in Fulify first. taxCategoryId,customsTemplateIdandshippingProfileIdmust be ids that belong to your organization.
Prices, weights and dimensions
Send prices, weights and dimensions, on products and on variants, as a number such as 59 or a string such as "59.00". Send null to clear a value.
Fulify checks only that the value is a number or a string, not that it's a valid decimal, so validate it on your side. A product price that isn't a plain decimal, such as "abc", "" or "12,50", passes validation and dry runs, then fails that product with write failed. Weights, dimensions and variant prices are stored exactly as sent, and negative numbers aren't refused.
Product prices are stored to two decimal places, so "9.999" is saved as "10.00".
Fields Fulify ignores
A product's read shape includes fields you can't write. When you send one of these, Fulify ignores it:
brandIdandcategoryIdsare read-only and are ignored without a warning. Usebrandandcategoriesto set them.- Field names Fulify doesn't recognise are ignored without a warning, so check your field names against the data schema.
- Server-owned fields copied from a read, such as
id,syncStatus,bigcommerceId,lastSyncedAt,createdAtandupdatedAt, are skipped without a warning.
Results
The response is always 200 when the request itself is valid, even if some products fail. Check each entry in results, which are in the same order as your request.
| Field | Meaning |
|---|---|
| sku | The product's SKU. |
| action | created, updated or error. |
| id | The product's id. Not present on errors or dry runs. |
| errors | Why the product failed. Present when action is error. |
| warnings | Things Fulify wants you to know, such as a product opted in to Linnworks while isActive is false. |
| variantsUpdated | How many existing variants were updated. |
| variantsCreated | How many new variants were created. |
| createdOptionValues | Option values created for new variants, as "Type: Value". |
| unmatchedVariants | Variant SKUs that weren't updated or created, with the reason. |
A failed product looks like this:
summary counts the outcomes across the request and lists unresolved brands and categories. It also has createdOptionValues when any option values were created. When the request starts a Linnworks sync, the response has a top-level linnworks object; see Sync control.
One product never affects another
Each product is written in its own transaction. If one fails, the others are still written, and the failed product is left exactly as it was, with nothing half-written. A product that fails for an unexpected reason during the write, after passing validation, comes back with the error write failed.
If the same SKU appears twice in one request, the second entry fails with duplicate sku in this request.
Dry runs
Add ?dryRun=true to validate the request and see what would happen without writing anything:
A dry run reports action, errors, variantsCreated and createdOptionValues. It doesn't return ids, doesn't report warnings, variantsUpdated or unmatchedVariants, and doesn't start a Linnworks sync. Scope checks still apply.
What happens after a write
- The write lands in Fulify only. Nothing is sent to BigCommerce or Shopify by the API call itself.
- If the product was already synced and has Channel Sync on, its sync status changes to
pendingso the next sync sends the change. - Every product created or updated fires a
product.createdorproduct.updatedwebhook. - The change appears in the product's history, attributed to the API key.
Sales-channel assignment (which BigCommerce or Shopify stores a product goes to) isn't writable through the API. Assign channels in Fulify.