Getting Started With SCAYLE
Import Products
- Getting started
- Tech
Robert Merten
VP Tech
By the end of this tutorial, you will have successfully set up a fully functional fashion store, equipped with its own product catalog. We'll begin by establishing the foundational data needed for our store. To launch our fashion shop, we will create the following entities:
You have a choice between importing some demo data using the SCAYLE Panel, or manually creating your own data using the Admin API.
Using demo data (Option 1) allows you to quickly test the setup by using the SCAYLE Panel.
To get a better understanding of how different SCAYLE entities relate to each other, use Admin API (Option 2).
Both approaches allow you to learn how to work with SCAYLE, the choice is simply a matter of preference.
Getting Started With SCAYLE
Robert Merten
VP Tech
To use demo data, you'll need to import it via the SCAYLE Panel. You can find it by going to Settings > General > Configuration > Import Demo Data
. You can import demo data for products, customers, and orders. To import orders, note that you must first import product and customer demo data.
To import demo data:
The Demo Data will create all entities needed for the complete shop setup. \
Once all Demo Data is imported in the SCAYLE Panel, a CSV file is generated. You'll need this later to add attribute values to your products.
The Admin API is a REST API used to interact with customer and transaction data, as well as import products and stock. You'll use this API to set up and manage all of your shop data. In the following sections, we'll walk you through the basics of working with the Admin API.
For a more in-depth explanation of the Admin API and all its features, see our Developer Guide.
SCAYLE provides an SDK for testing your connection to the Admin API by using the SwaggerClient.
Start by creating a file named src/001_validateConnection.js
Then, install the client:
npm install swagger-client
const SwaggerClient = require("swagger-client");
async function validateConnection() {
const client = await new SwaggerClient({
url: `https://${TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: ADMIN_API_TOKEN,
},
},
});
}
validateConnection();
As you see, we need a TENANT_KEY
and a ADMIN_API_TOKEN
before we can proceed.
Let's create a small file .env
to hold these credentials. If you prefer, you can also just copy and paste them every time you initialize the SwaggerClient.
TENANT_KEY={YOUR_TENANT_KEY}
ADMIN_API_TOKEN={YOUR_ADMIN_API_TOKEN}
You can find your tenant key in the SCAYLE Panel.
For example, if your SCAYLE Panel URL is https://acme-demo.panel.scayle.cloud/shops/10000 , thenacme-demo
is your tenant key.
To check if the Admin API is properly set up, visit https://${YOUR_TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json
You should see a OpenAPI configuration JSON.
To receive your ADMIN_API key, log into the SCAYLE Panel and navigate toSettings > General > API Keys.
Create a token and copy it to your .env
file. It should look something like this:
TENANT_KEY=acme-demo
ADMIN_API_TOKEN=0eede91b3e80bab1b0302e6ea50ec969b4f12a306d8bb8daa761c86066e141cc
Let's add dotenv
to read our .env
file and exchange TENANT_KEY and ADMIN_API_TOKEN:
npm install dotenv
const SwaggerClient = require("swagger-client");
require("dotenv").config();
// Making sure that you can connect to the Admin API
// Hint: It might be, that this call returns an empty list, as we didn't create any shops yet.
// We just care about the response code.
async function validateConnection() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
}
validateConnection();
It can take a while before you receive a response. Wait at least 10 minutes until proceeding with the rest of this tutorial.
With authentication complete, we can finally test our connection. We'll do this by calling the getShops
endpoint.
The response might be an empty list. This is expected, as right now we only care about ensuring the connection works.
const SwaggerClient = require("swagger-client");
require("dotenv").config();
// Making sure that you can connect to the Admin API
// Hint: It might be, that this call returns an empty list, as we didn't create any shops yet.
// We just care about the response code.
async function validateConnection() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
try {
const response = await client.apis.Shops.getShops();
if (response.ok) {
console.info("Successfully connected to the Admin API.");
}
} catch (e) {
console.log(e.response.body.errors)
}
}
validateConnection();
Use breakpoints in line 20 and 23 to quickly check if the call was successful.
You will find the reason for any potential error responses in error.response.body.errors
.
Every error will have an errorKey
and a message that explains what happened.
For example, if you didn't wait long enough to receive the access key, you might receive the following error response:
{errorKey: 'INVALID_ACCESS_TOKEN', message: 'Invalid or unknown access token is provided via the X-Access-Token header.', context: {…}}
If this doesn't resolve after a maximum of 20 minutes, contact your SCAYLE Customer Success Manager for assistance.
Now that we've confirmed our connection to the Admin API, we can start working with our data. In the next section, we'll create a shop.
Now the fun part starts. We want to add real data our project. First, we need to create a shop.
The shop structure consists of two levels:
Let's start by setting up the addShop
function and our SwaggerClient.
Create file src/002_addShop.js:
const SwaggerClient = require("swagger-client");
require("dotenv").config();
async function addShop() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
}
addShop();
Your shop needs a name
and a key
that is unique across all shops that you create in your environment. This key must be two characters long.
We can add one or multiple countries, depending on how many markets you want to serve with this shop.
The countryCode
should be one of these codes.
For possible defaultLanguageCode,
refer to this list.
For possible currencyCode
refer to this list.
const shop = {
name: "My Shop",
key: "ms", // 2 characters (!)
countries: [
{
countryCode: "de",
defaultLanguageCode: "en_GB", // ch_CH,ch_FR
currencyCode: "EUR", // USD,CAD
url: "http://www.my_shop.com",
},
],
};
Once created, shops can't be deleted.
For this tutorial, we'll create a shop for the German market. However, we'll set the default language for this shop to English.
With our data in place, we can call the Admin API to create the shop:
try {
let response = await client.apis.Shops.createShop(
{},
{ requestBody: shop }
);
let createdShop = response.body;
console.log("Created Shop", createdShop);
} catch (error) {
console.error("Returned errors", error.response.body.errors);
}
If the call is successful, you will receive the ShopID
:
{
id: 10002,
key: "ms",
name: "My Shop",
logoUrl: null,
active: true,
deleted: false,
priceGroupKey: null,
shopCategoryTreeId: 1,
companyId: 1000,
}
As with other calls, check error.response.body.errors
to see the reason for any possible error responses.
For example, if the key you want to use already exists, you'll see the following:
{
context: {key: 'ms'},
errorKey: 'SHOP_KEY_ALREADY_USED',
message: 'The provided shop key is already in use'
}
See the Admin API reference documentation for the full list of possible errors.
Navigate to Shops. You should now be able to see your newly created shop.
const SwaggerClient = require("swagger-client");
require("dotenv").config();
async function addShop() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
const shop = {
name: "The Kernel Kiosk 2",
key: "k2", // Must be 2 chars long
countries: [
{
countryCode: "de", // en, gb, de, fr, es
defaultLanguageCode: "en_GB", // en_GB, en_US, de_DE, fr_FR, es_ES
currencyCode: "EUR", // USD, EUR, GBP, JPY, CHF
url: "http://www.kernel_kiosk.com",
},
],
};
try {
let response = await client.apis.Shops.createShop(
{},
{ requestBody: shop }
);
let createdShop = response.body;
console.log("Created Shop", createdShop);
} catch (error) {
console.error("Errors", error.response.body.errors);
}
}
addShop();
With our shop created, we now need to add Attribute Groups to categorize our products. An Attribute Group is a collection of different attribute values. This information is used to define a product and its characteristics. All the information a customer may need to know about a product should be stored in an Attribute Group. For example, for a product with the attribute values of red and green, you would want to collect them in an Attribute Group called color.
Before we can proceed, we need to take a look how products are structured in SCAYLE.
A product can have 3 different layers:
The first layer is the master layer, which contains generic information about a particular product, such as name and brand.
The second layer holds a differentiating Attribute Group about the product, such as color.
In the last layer, we store information about our product's size, price and stock. In SCAYLE, we refer to these as variants.
For example, if we want to sell shirts in our shop, we can categorize these shirts by brand, model, and color. These shirts can also have different sizes, with each size available in different quantities in a warehouse, and offered at different prices.
In our shop's product model, the master layer would be our shirts' brand and model. The product layer would be our shirts' color, and the variant layer would be our shirts' sizes and prices. If we want to stock red and green men's shirts in sizes S, M and L, we would then want to create Attribute Groups for color and size.
Confused? Our Onboarding guide offers a more in-depth explanation of these topics.
Now that we've decided on a set of Attribute Groups that we need in our shop (color and size), we need to create them with the Admin API. We will add the corresponding attribute values (red, green, S, M, and L) later on, when we create our products. (We'll skip adding the brand and model attribute groups, to avoid overwhelming you with information. You can add these on your own later, following the same process as outlined in this tutorial.)
As before, let's quickly setup our base setup at src/003_createAttributeGroups.js:
const SwaggerClient = require("swagger-client");
require("dotenv").config();
async function createAttributeGroups() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
// Implement
}
createAttributeGroups();
Let's first define the Attribute Groups we want to add to our system.
name | Name used internally. This won't be displayed to the customer. |
---|---|
frontendName | Name that will displayed to the customer. You will need to provide translations. |
type | Single select attribute. A product can only have one value for this attribute at a time. |
isShared | Will this attribute be used by other shops in your instance? Set to true or false . |
level | Can either be on the master, product or variant level. |
isDifferentiating | This attribute differentiates between different variants. No two variants can have the same value for this attribute. Set to true or false . |
const attributes = [
{
name: "color",
frontendName: {
de_DE: "Farbe",
en_GB: "Color",
},
type: "localizedString",
isShared: true,
level: "product", // every product below the master has a unique color
isDifferentiating: true,
},
{
name: "size",
frontendName: {
de_DE: "Groesse",
en_GB: "Size",
},
type: "localizedString",
isShared: true,
level: "variant", // Every variant below the product has a unique size
isDifferentiating: true,
}
];
With our two Attribute Groups defined, let's call the Admin API to add them to our system.
for (const attribute of attributes) {
try {
const response = await client.apis.AttributeGroups.createAttributeGroup(
{},
{ requestBody: attribute }
);
const createdAttributeGroup = response.body;
console.log(createdAttributeGroup);
} catch (error) {
console.error(
"Unable to create attribute group",
error.response.body.errors
);
}
}
NAME_IS_NOT_UNIQUE | Attribute Group with name already exists |
---|---|
NOT_SUPPORTED_FOR_LEVEL | Only shared Attribute Group are supported for the given level |
MISSING_BASE_LOCALE | The base locale must be provided |
ONLY_SUPPORTED_FOR_ADVANCED_TYPES | Structure is only supported for advanced types |
You can always delete Attribute Groups, as long as you haven't added them to any products:
for (const attribute of attributes) {
await client.apis.AttributeGroups.deleteAttributeGroup({
attributeGroupName: attribute.name,
});
}
const SwaggerClient = require("swagger-client");
require("dotenv").config();
async function createAttributeGroups() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
const attributes = [
{
name: "size",
frontendName: {
de_DE: "Groesse",
en_GB: "Size",
},
type: "localizedString", // single select attribute. A product can only have one value for this attribute at a time.
isShared: true, // This attribute is shared between all Shops in your instance
level: "variant", // Where this attribute is used. Can be master, product or variant
isDifferentiating: true, // This attribute is used to differentiate between different variants. No two variants can have the same value for this attribute.
},
{
name: "color",
frontendName: {
de_DE: "Farbe",
en_GB: "Color",
},
type: "localizedString",
isShared: true,
level: "product",
isDifferentiating: true,
},
];
for (const attribute of attributes) {
try {
const response = await client.apis.AttributeGroups.createAttributeGroup(
{},
{ requestBody: attribute }
);
const createdAttributeGroup = response.body;
console.log(createdAttributeGroup);
} catch (error) {
console.error(
"Unable to create attribute group",
error.response.body.errors
);
}
}
}
createAttributeGroups();
Navigate to Settings > Attributes
. You should now see the Attribute Groups you've created in your shop:
Master categories are used to categorize different product types (such as "Men's shirts" or "Cotton shirts").
Master categories are different from shop categories, which are used to group products for customers of your shop. For example, you might have a "New" category, which groups a lot of different product types (shirts, trousers, hats) into one category.
As we want to import men's shirts first, let's create the master categories we need:
const SwaggerClient = require("swagger-client");
require("dotenv").config();
async function createMasterCategories() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
}
createMasterCategories();
To create a master category, only a path is required. Master categories need to be created one by one, and the parent category must already exist.
Notice that you can define Attribute Groups that are mandatory for a category. Non-parent master categories will then inherit this configuration, and won't require defining.
We need to ensure that all products of the type clothing have a color. Otherwise, you won't be able to add this master category to a product.
Name | Description |
---|---|
path | An array of identifiers that are used for internal identification. This will never be exposed to customers. |
attributes | Attribute Groups that are attached to this master category. |
const masterCategories = [
{
path: ["Men"],
},
{
path: ["Men", "Clothing"],
attributes: [
{
name: "color",
type: "simple",
isMandatory: true,
},
],
},
{
path: ["Men", "Clothing", "Shirts"],
},
];
for (const masterCategory of masterCategories) {
try {
const response = await client.apis.MasterCategories.createMasterCategory(
{},
{ requestBody: masterCategory }
);
const createdMasterCategory = response.body;
console.log(createdMasterCategory);
} catch (error) {
console.error(
"Unable to create master category",
error.response.body.errors
);
}
}
Error | Details |
---|---|
MASTER_CATEGORY_DUPLICATE_DETECTED | A master category with the same path already exists |
MASTER_CATEGORY_PARENT_DOES_NOT_EXIST | The parent master category does not exist for provided path |
ATTRIBUTE_GROUP_NOT_EXISTING | Attribute Group does not exist |
Navigate to Settings > Categories
. You should see your master category tree in your shop:
const SwaggerClient = require("swagger-client");
require("dotenv").config();
async function createMasterCategories() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
const masterCategories = [
{
path: ["Men"],
},
{
path: ["Men", "Clothing"],
attributes: [
{
name: "color",
type: "simple",
isMandatory: true,
},
],
},
{
path: ["Men", "Clothing", "Shirts"], // We don't need to define mandatory attributes here, as this has been covered by the Clothing Category
},
];
for (const masterCategory of masterCategories) {
try {
const response = await client.apis.MasterCategories.createMasterCategory(
{},
{ requestBody: masterCategory }
);
const createdMasterCategory = response.body;
console.log(createdMasterCategory);
} catch (error) {
console.error(
"Unable to create master category",
error.response.body.errors
);
}
}
}
createMasterCategories();
We've created our shop, and we've defined our products. There's just one step left before we can finally import our products into the shop.
When you sell physical products, these products need to be stored somewhere. In SCAYLE, we define these locations as warehouses (you might also have virtual warehouses that present stock from your stores, but that's a use case that is outside the scope of this tutorial).
For example, our shop might store our red men's shirt in size M in multiple warehouses. We can define which warehouse should have priority on the country level.
Here's a scenario for our shop. We have two warehouses for our DE shop, one in DE and one in NL. We'll set a higher priority for the DE warehouse, because shipping from this location for DE customers should result in a quicker delivery time for the customer.
Both warehouses have the red men's shirt in stock.
Warehouse | Location | Shirt Stock | Priority |
---|---|---|---|
A | DE | 10 | 10 |
B | NL | 10 | 1 |
Now when a customer orders the shirt in our online shop, we will ship it from Warehouse A, the one located in DE. The customer will also see an accurate delivery estimation based on a config we set for that warehouse.
But what if the scenario is slightly different? What if there's no stock in Warehouse A?
Warehouse | Location | Shirt Stock | Priority |
---|---|---|---|
A | DE | 0 | 10 |
B | NL | 10 | 1 |
In this case, we will ship from the NL warehouse, because there is no stock in the warehouse with the highest priority. The customer will also see a different delivery estimate.
In the code, you will see a merchant attached to a warehouse. This is important for marketplace shops, which might have stock from different merchants in their warehouse. Similar to the warehouse priority, we can also set a priority for different merchants, as it might make sense to sell stock from particular merchants first.
const SwaggerClient = require("swagger-client");
require("dotenv").config();
async function createAndAttachWarehouse() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
}
createAndAttachWarehouse();// Some code
Let's first take steps to create the warehouse. It only takes a referenceKey
for internal identification:
const newWarehouse = {
referenceKey: "WarehouseDE",
};
let createdWarehouse = null; // we need to store the created warehouse to attach the merchant to it
try {
const response = await client.apis.Warehouses.createWarehouse(
{},
{ requestBody: newWarehouse }
);
createdWarehouse = response.body;
} catch (error) {
console.error(
"Unable to create shop country warehouse",
error.response.body.errors
);
process.exit(1); // Make sure to stop the script
}
WAREHOUSE_ALREADY_EXIST | Warehouse with given referenceKey already exists |
Now we need to attach the newly created warehouse to the default merchant. (You don't need to care about the behavior of merchants right now.)
Let's assume for now that our company procures all products from one merchant.
try {
const response = await client.apis.Warehouses.attachMerchantWarehouse({
merchantIdentifier: 1, // default warehouse that is created with the instance
warehouseIdentifier: createdWarehouse.id,
});
console.log("Attached Merchant to Warehouse", response.body);
} catch (error) {
console.error(
"Unable to attach merchant to warehouse",
error.response.body.errors
);
process.exit(1); // Make sure to stop the script
}
WAREHOUSE_NOT_FOUND | Make sure the Warehouse was successfully created in the step before |
MERCHANT_NOT_FOUND | Merchant not found |
We need to attach the Warehouse to a ShopCountry, and set the priority that we mentioned at the start of this chapter.
try {
const newShopCountryWarehouse = {
referenceKey: "WarehouseDE",
priority: 100,
};
const response = await client.apis.Warehouses.createShopCountryWarehouse(
{
shopKey: "ms",
countryCode: "de",
},
{
requestBody: newShopCountryWarehouse,
}
);
const createdShopCountryWarehouse = response.body;
console.log("Created Shop Country Warehouse", createdShopCountryWarehouse);
} catch (error) {
console.error("Unable to create warehouse", error.response.body.errors);
process.exit(1);
}
WAREHOUSE_NOT_FOUND | Make sure the Warehouse was successfully created in the step before |
SHOP_COUNTRY_NOT_FOUND | Shop country not found |
SHOP_WAREHOUSE_ALREADY_EXISTS | The provided warehouseReferenceKey already exists for given shop |
const SwaggerClient = require("swagger-client");
require("dotenv").config();
async function createAndAttachWarehouse() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
const newWarehouse = {
referenceKey: "Warehouse11",
};
let createdWarehouse = null;
try {
const response = await client.apis.Warehouses.createWarehouse(
{},
{ requestBody: newWarehouse }
);
createdWarehouse = response.body;
} catch (error) {
console.error(
"Unable to create shop country warehouse",
error.response.body.errors
);
process.exit(1);
}
try {
const response = await client.apis.Warehouses.attachMerchantWarehouse({
merchantIdentifier: 1, // default warehouse that is created with the instance
warehouseIdentifier: createdWarehouse.id,
});
console.log("Attached Merchant to Warehouse", response.body);
} catch (error) {
console.error(
"Unable to attach merchant to warehouse",
error.response.body.errors
);
process.exit(1);
}
try {
const newShopCountryWarehouse = {
referenceKey: "Warehouse11",
priority: 100,
};
const response = await client.apis.Warehouses.createShopCountryWarehouse(
{
shopKey: "k2",
countryCode: "de",
},
{
requestBody: newShopCountryWarehouse,
}
);
const createdShopCountryWarehouse = response.body;
console.log("Created Shop Country Warehouse", createdShopCountryWarehouse);
} catch (error) {
console.error("Unable to create warehouse", error.response.body.errors);
process.exit(1);
}
}
createAndAttachWarehouse();
Navigate to Settings > Merchant Management > Default > Warehouses
. You should see the warehouse you just created:
With all the groundwork complete, we're ready to import our products into our shop.
To make this easier, this tutorial provides demo data and simplifies the import process for the sake of brevity.
We recommend that you do a deep dive into product data management later in our Onboarding guide.
We will use this CSV as a starting point. The CSV contains the following fields. Note that you would normally have different data sources, or would first create a basic product, and then add stock and price information later.
We'll skip these steps to get you up to speed as fast as possible.
Copy
referenceKey,name,state,categories,color,size,imageSource,price,tax,currencyCode,countryCode,quantity,warehouse,sustainable
THS1234,Tom Tailor Shirt,live,"Men,Clothing,Shirts",blue,"S,M,L","https://cdn.aboutstatic.com/file/images/7eaab7a1547c73c286d74068515fb5fc.jpg?brightness=0.96&quality=75&trim=1&height=1280&width=960",3990,19.0,EUR,de,10,WarehouseDE,true
THS1235,Levis T-Shirt,live,"Men,Clothing,Shirts",gray,"M,XL","https://cdn.aboutstatic.com/file/images/68bf124a0517bffaa5fe1dc7ac8707db.jpg",3990,19.0,EUR,de,100,WarehouseDE,false
THS1236,Tom Tailor Shirt,live,"Men,Clothing,Shirts",blue,"S,M,XL","https://cdn.aboutstatic.com/file/images/f7268d764b2b00648da7edb8f5fb5e47.jpg",3990,19.0,EUR,de,20,WarehouseDE,false
THS1237,Tom Tailor Shirt,live,"Men,Clothing,Shirts",gray,S,"https://cdn.aboutstatic.com/file/images/838cf719621c4727f5927aa812c866cc.jpg",3990,19.0,EUR,de,5,WarehouseDE,true
and save it in your project under ./data/products.csv
.
The CSV contains basic data to create a product.
Product Information | |
---|---|
referenceKey | A unique key that we will use to identify the product. Usually this is set by your PIM |
name | well... e.g. Tommy Hilfiger Shirt |
state | In our case, all products are live. You can also define a product to be a "draft" so it doesn't go live straight away |
categories | A list of master categories. In our case all belong to the Male -> Clothing -> Shirts category we defined earlier |
color | The value of the color attribute for this product |
A list of sizes for this product. | |
imageSource | An image of the product. Notice the file endings (".png"), these are used by the Admin API to determine the file type |
sustainable | Is this product sustainable? We will need this information later |
Price & Size & Stock information | ⚠️ Ordinarily you wouldn't define these once per product. You would define them per Variant. The Price, Quantity and Warehouse can of course differ by size (a.k.a variant) |
size | List of sizes for this product. |
price | Price in decimals |
countryCode | CountryCode |
currencyCode | CurrencyCode |
quantity | Quantity of your stock |
warehouse | The warehouse where the stock lies |
We start with the same base setup as before. Initialize the SDK Client:
const SwaggerClient = require("swagger-client");
require("dotenv").config();
async function importProducts() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
}
importProducts();
As we want to read a CSV file, let's install a package to help us do that.
We'll use papaparse, because it translates every line of the CSV into an easy to access object.
In the project directory execute:
pnpm install papaparse
and add:
const Papa = require("papaparse");
const fs = require("fs");
Next we will adjust our importProducts
function to read our products.csv.
We first read the file and pass it to papaparse to receive a list of objects. These objects will have the header of our CSV as fields, for example:
{referenceKey: "THS1234", name: "Hilfiger Shirt", ...}
Notice that we call a function with the result, and pass our adminAPI client as well. We'll implement this function next.
fs.readFile("./data/products.csv", "utf8", function (err, data) {
if (err) {
console.error("Error reading the CSV file:", err);
return;
}
Papa.parse(data, {
// Makes sure that we get the data as an array of objects
// in the format {referenceKey: "THS1234", name: "Hilfiger Shirt", ...}
header: true,
complete: function (results) {
for (const product of results.data) {
createProduct(client, product);
}
},
error: function (error) {
console.log("Unable to parse products.csv", error);
},
});
});
Let's implement the createProduct
function:
async function createProduct(client, data) {
/// Implement...
}
This function converts the CSV data structure into the AdminAPI data structure.
const newProduct = {
referenceKey: data.referenceKey,
name: {
de_DE: data.name,
en_GB: data.name, // We assume here that the name will work for all languages
},
state: data.state, // live or draft
master: {
referenceKey: `${data.referenceKey}-master`, // Usually we would receive a master key and not set it like this
categories: {
paths: [data.categories.split(",")], // These categories need to be defined beforehand
},
},
attributes: [
{
name: "color", // This attribute group needs to be present
type: "localizedString",
value: {
de_DE: data.color, // these attribute values will automatically be created
en_GB: data.color,
},
},
],
images: data.imageSource.split(",").map((imageSource) => {
return {
source: {
url: imageSource.trim(), // Make sure that the URL contains the filetype ({filename}.png/jpg)
},
};
}),
// Usually you would set prices and stock at a later point. They also might differ between
// variants and not on product level like in the CSV.
variants: data.size.split(",").map((size) => {
return {
referenceKey: `${data.referenceKey}-${size}`,
attributes: [
{
name: "size",
type: "localizedString",
value: {
de_DE: size,
en_GB: size,
},
},
],
prices: [
{
price: parseInt(data.price), // Make sure to pass decimals like 3900 (39.00 EUR)
tax: parseInt(data.tax),
currencyCode: data.currencyCode,
countryCode: data.countryCode,
},
],
stocks: [
{
quantity: parseInt(data.quantity),
warehouseReferenceKey: data.warehouse,
changedAt: "2024-01-26T00:00:00+00:00",
merchantReferenceKey: "default",
},
],
};
}),
};
Finally, we pass our converted product to the Admin SDK:
try {
let response = await client.apis.Products.createProduct(
{},
{ requestBody: newProduct }
);
let createdProduct = response.body;
console.log("Created Product:", createdProduct);
} catch (error) {
console.error("Unable to create product", error.response.body.errors);
}
PRODUCT_ALREADY_EXISTS | Product already exists |
REQUEST_SCHEMA_VALIDATION_FAILED | The request does not match the necessary schema |
INVALID_LOCALE | The provided locale does not exist |
If you want to delete products, you can do that by creating a function similar to createProduct, like so:
async function deleteProduct(client, data) {
try {
let response = await client.apis.Products.deleteProduct({
productIdentifier: `key=${data.referenceKey}`, // notice the key={}, alternatively you can use the productIds
});
console.info(`Deleted product with reference key ${referenceKey}`);
} catch (error) {
console.error("Unable to delete product", error.response.body.errors);
}
}
Navigate to Shops > My Shop > Products
and select Germany (en_GB)
Selecting a product opens the product detail view:
const SwaggerClient = require("swagger-client");
require("dotenv").config();
const Papa = require("papaparse");
const fs = require("fs");
// Create a product according to the documentation found here: https://scayle.dev/en/dev/admin-api/create-product
// Learn more about the product structure here: https://scayle.dev/en/dev/admin-api/product-overview
async function createProduct(client, data) {
const newProduct = {
referenceKey: data.referenceKey,
name: {
de_DE: data.name, // We assume here that the name will work for all languages
en_GB: data.name,
},
state: data.state, // live or draft
master: {
referenceKey: `${data.referenceKey}-master`, // Usually we would receive a master key and not set it like this
categories: {
paths: [data.categories.split(",")], // These categories need to be defined beforehand
},
},
attributes: [
{
name: "color", // This attribute group needs to be present
type: "localizedString",
value: {
de_DE: data.color, // these attribute values will automatically be created if they don't exist
en_GB: data.color,
},
},
],
images: data.imageSource.split(",").map((imageSource) => {
return {
source: {
url: imageSource.trim(), // Make sure that the URL contains the filetype ({filename}.png/jpg)
},
};
}),
// Usually you would set prices and stock at a later point. They also might differ between
// variants and not on product level like in the CSV.
variants: data.size.split(",").map((size) => {
return {
referenceKey: `${data.referenceKey}-${size}`,
attributes: [
{
name: "size",
type: "localizedString",
value: {
de_DE: size,
en_GB: size,
},
},
],
prices: [
{
price: parseInt(data.price), // Make sure to pass decimals like 3900 (39,00 EUR)
tax: parseInt(data.tax),
currencyCode: data.currencyCode,
countryCode: data.countryCode,
},
],
stocks: [
{
quantity: parseInt(data.quantity),
warehouseReferenceKey: data.warehouse,
changedAt: "2024-01-26T00:00:00+00:00",
merchantReferenceKey: "default",
},
],
};
}),
};
try {
let response = await client.apis.Products.createProduct(
{},
{ requestBody: newProduct }
);
let createdProduct = response.body;
console.log("Created Product:", createdProduct);
} catch (error) {
console.error("Unable to create product", error.response.body.errors);
}
}
// Import products from our products.csv file
// The CSV is simplified. In a production import you would probably have a different way of importing the data from your system.
// This is a general demonstration on how to use the Admin API to create products in SCAYLE.
async function importProducts() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
fs.readFile("./data/products.csv", "utf8", function (err, data) {
if (err) {
console.error("Error reading the CSV file:", err);
return;
}
Papa.parse(data, {
// Makes sure that we get the data as an array of objects
// in the format {referenceKey: "THS1234", name: "Hilfiger Shirt", ...}
header: true,
complete: function (results) {
for (const product of results.data) {
createProduct(client, product);
}
},
error: function (error) {
console.log("Unable to parse products.csv", error);
},
});
});
}
importProducts();
In order for us to see the products in our shop, we need to complete one last step, and that is creating shop categories.
For now, we will map these one-to-one to our master categories, as we also want to have a shirts category. So our master category (Men -> Clothing -> Shirts) will map to the same shop categories.
It's easy to imagine that we'd want different combinations though. So, we'll create a New category that will contain all products in our shop, regardless of type. We'll also create a Cotton Shirt category that will contain all shirts that also have the Material -> Cotton attribute.
Initialize the client:
const SwaggerClient = require("swagger-client");
require("dotenv").config();
async function createShopCategories() {
const client = await new SwaggerClient({
url: `https://${process.env.TENANT_KEY}.admin.api.scayle.cloud/api/admin/v1/admin-api.json`,
authorizations: {
accessToken: {
value: process.env.ADMIN_API_TOKEN,
},
},
});
}
createShopCategories();
parentId | ID of the parent category. In our example, the clothing category will have the parentID of the Male category |
name | Translated name of the category. You can define as many languageKeys as you've defined Shop Languages |
leftSiblingId | This is used to sort categories in the category tree. If we would like to create a Men->Clothing->Trousers category, we would set the leftSiblingId to the id of the Shirts category so that the Trousers category appears AFTER the SHirts category. |
isActive | If a category is active, you can pass this category id to the storefrontAPI to filter products. This will be explored later in the guide |
isVisible | Should this category be visible to the user? This is helpful, if you have a special newsletter or campaign where you can only access a category by a direct link |
productSets | Here you can define which products should be part of the category. |
const categories = [
{
parentId: null,
name: {
de_DE: "Male",
en_GB: "Male",
},
isActive: true,
isVisible: true,
productSets: [
{
attributes: [
{
name: "category",
include: ["Men"], // Should contain all products that have the master category "Men"
},
],
},
],
},
{
parentId: null, // Will be set later to the id of the "Male" category
name: {
de_DE: "Clothing",
en_GB: "Clothing",
},
productSets: [
{
attributes: [
{
name: "category",
include: ["Men|Clothing"], // Should contain all products that have the master category "Clothing"
},
],
},
],
isActive: true,
isVisible: true,
},
{
parentId: null, // Will be set later to the id of the "Clothing" category
name: {
de_DE: "Shirts",
en_GB: "Shirts",
},
productSets: [
{
attributes: [
{
name: "category",
include: ["Men|Clothing|Shirts"], // Should contain all products that have the master category "Shirts"
},
],
},
],
isActive: true,
isVisible: true,
},
];
let parentID = null;
for (let category of categories) {
category.parentId = parentID;
try {
const response = await client.apis.ShopCategories.createShopCategory(
{ shopKey: "ms" },
{ requestBody: category }
);
const createdShopCategory = response.body;
parentID = createdShopCategory.id; // We get the parentId from the last created category
console.info("Created Category", createdShopCategory);
} catch (error) {
console.error(
"Unable to create shop category",
error.response.body.errors
);
}
}
Navigate to Shops > My Shop > Products > Categories
.
With data added to your shop, it's time to learn how to set up our Storefront.