Inventory & Products | Products | Multi-Location | Set Products Multi

Setting Product Multi-Location Data

This document outlines the procedure for making API calls to manage product multi-location data via https://easycms.fi/public_api/set_products_multi/.

Important Notes

  • All parameters marked as MANDATORY are required
  • This endpoint manages location-specific pricing, barcodes, supplier info, and time-based scheduled pricing
  • Each product can have different prices/barcodes per location
  • Each row has a priority order (row_num) — lower numbers are evaluated first for scheduled pricing
  • Supports CREATE, UPDATE, and DELETE operations
  • When multiple rows exist for a product, the system evaluates them in row_num order and the first matching schedule wins
  • price is the net sell price (3 decimals) — the same value the POS stores; gross = net × (1 + VAT/100)
  • Rows also carry three discount ladder flags (discount_lock, ignore_qty_discount, ignore_bxgx) that control what the row's discount overrides — see Updatable Fields — Discount Ladder Flags below (platform version 3.89+)

Scheduled Pricing (Happy Hour)

Products can have time-based price overrides that activate during specific time windows. A scheduled pricing row defines:

  • Time range: stime / etime (e.g., lunch pricing 11:00–14:00)
  • Date range: sdate / edate (e.g., summer promotion June–August)
  • Day of week: days_list (e.g., weekdays only: monday–friday)
  • Priority: row_num — when multiple schedules could match, the row with the lowest row_num wins

If all scheduling fields are NULL, the row is considered always active (a permanent override). A row with scheduling fields is only active when all conditions match (current time is within time range AND date range AND day of week).

SETTER API ENDPOINT FOR DATA INSERTION

Setter API calls are limited and not available for public API calls, these calls are only available for resellers or certain software providers and developers upon request.


Endpoint and Method

  • Endpoint: https://easycms.fi/public_api/set_products_multi/
  • Method: POST

Parameters | Payload

  • TOKEN (api_key): Your unique API key for authentication. How to Create API Credentials.
  • username: Your login username.
  • password: Your login password.
  • account: Your specific account ID.

Required Parameters (MANDATORY)

Parameter Type Description
pid integer Product ID - The product to manage multi-location data for

Conditionally Required Parameters

Parameter Type Description
location_id string Location ID - Required when no scheduling fields are provided. Optional when only scheduling fields (stime, etime, sdate, edate, days_list) are submitted.

Optional Parameters for Identification

Parameter Type Description
multi_id string If provided, updates/deletes the specific record by UUID. If not provided, finds record by pid + location_id combination. The multi_id for each record is returned by the get_products_multi endpoint.

Updatable Fields — Pricing & Barcodes

Parameter Type Description
code string Location-specific barcode/SKU
boxcode string Box code for this location
supplier_id integer Supplier ID for this location
crsupplier_id string Cash Register Supplier ID
pricebuy decimal Buy price at this location (3 decimals)
price decimal Sell price at this location - net price (3 decimals)
discount decimal Discount percentage at this location
row_num integer Priority order for this row (default: 0). Lower = higher priority. The first matching row wins when evaluating scheduled pricing.

Updatable Fields — Scheduled Pricing (Time-Based Pricing)

Parameter Type Description
stime time Start time for scheduled pricing. Format: HH:MM or HH:MM:SS (e.g., "11:00" or "11:00:00")
etime time End time for scheduled pricing. Format: HH:MM or HH:MM:SS (e.g., "14:00" or "14:00:00")
sdate date Start date for scheduled pricing. Format: YYYY-MM-DD (e.g., "2025-06-01")
edate date End date for scheduled pricing. Format: YYYY-MM-DD (e.g., "2025-08-31")
days_list string Comma-separated day names. Accepts full names ("monday,tuesday,wednesday"), abbreviations ("mon,tue,wed"), or "every_day" for all 7 days. Case-insensitive.

Valid day names (full): monday, tuesday, wednesday, thursday, friday, saturday, sunday
Valid abbreviations: mon, tue, wed, thu, fri, sat, sun

Updatable Fields — Discount Ladder Flags (platform version 3.89+)

These flags steer how the row's discount competes when the row wins the price resolution. Accept 1/0, true/false, "1"/"0", "true"/"false" (case-insensitive; also "yes"/"no", "on"/"off"). Any other value is rejected with INVALID_BOOLEAN_VALUE.

Parameter Type Description
discount_lock boolean While this row wins, its discount replaces the product's own discount and quantity discount tiers are skipped. Customer-specific prices and VIP discounts still compete (the bigger discount wins). Inert when the row carries no discount — a locked row with an empty discount keeps only its price override.
ignore_qty_discount boolean Quantity discount tiers are skipped on lines priced from this row.
ignore_bxgx boolean Buy-X-Get-X free units are not generated on lines priced from this row.

How the row's discount meets the product's discount. By default they compete and the bigger discount wins. A specific row — one carrying a location and/or its own barcode/boxcode — always replaces the product's own discount, even when smaller. discount_lock additionally blocks quantity tiers (never customer pricing). Rows resolve in row_num order; the last row carrying a value wins that value.

Availability. The flags require platform version 3.89+ (the columns ship with upgrade-3.89). On an account that predates them, the flag parameters are ignored and the response carries flag_fields_ignored: true with a note in message — all other fields still save normally.

Delete Mode

Parameter Type Description
delete integer Set to 1 to delete the record (requires multi_id or pid+location_id)



Operation Modes

CREATE Mode

When no existing record is found for the pid+location_id combination (or multi_id), a new record is created. If row_num is not specified, it defaults to 0.

UPDATE Mode

When an existing record is found (by multi_id or pid+location_id), it is updated with the provided fields.

DELETE Mode

When delete=1 is passed, the record is deleted. If the deleted record was the last multi-record for the product, the multi_code_supplier flag on the product is automatically set to 0.



Call Examples in Different Languages


# Create/Update multi-location data with scheduled pricing
curl -X POST 'https://easycms.fi/public_api/set_products_multi' \
-H 'Authorization1: TOKEN' \
-H 'Content-Type: application/json' \
-d '{
  "username": "USERNAME",
  "password": "PASSWORD",
  "account": "ACCOUNT_ID",
  "pid": 12345,
  "location_id": "loc_001",
  "code": "LOC-SPECIFIC-BARCODE",
  "price": 25.990,
  "pricebuy": 15.500,
  "supplier_id": 10,
  "discount": 5.00,
  "stime": "11:00",
  "etime": "14:00",
  "days_list": "mon,tue,wed,thu,fri",
  "row_num": 1
}'

# Create/Update without scheduled pricing
curl -X POST 'https://easycms.fi/public_api/set_products_multi' \
-H 'Authorization1: TOKEN' \
-H 'Content-Type: application/json' \
-d '{
  "username": "USERNAME",
  "password": "PASSWORD",
  "account": "ACCOUNT_ID",
  "pid": 12345,
  "location_id": "loc_001",
  "code": "LOC-SPECIFIC-BARCODE",
  "price": 25.990,
  "pricebuy": 15.500,
  "supplier_id": 10,
  "discount": 5.00
}'

# Delete multi-location data
curl -X POST 'https://easycms.fi/public_api/set_products_multi' \
-H 'Authorization1: TOKEN' \
-H 'Content-Type: application/json' \
-d '{
  "username": "USERNAME",
  "password": "PASSWORD",
  "account": "ACCOUNT_ID",
  "pid": 12345,
  "location_id": "loc_001",
  "delete": 1
}'

$curl = curl_init();
curl_setopt_array($curl, array(
  CURLOPT_URL => "https://easycms.fi/public_api/set_products_multi",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode([
    'username' => 'USERNAME', 
    'password' => 'PASSWORD', 
    'account' => 'ACCOUNT_ID',
    'pid' => 12345,
    'location_id' => 'loc_001',
    'code' => 'LOC-SPECIFIC-BARCODE',
    'price' => 25.990,
    'pricebuy' => 15.500,
    'supplier_id' => 10,
    'discount' => 5.00,
    'stime' => '11:00',
    'etime' => '14:00',
    'days_list' => 'mon,tue,wed,thu,fri',
    'row_num' => 1
  ]),
  CURLOPT_HTTPHEADER => array(
    "Authorization1: TOKEN",
    "Content-Type: application/json"
  ),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;

import requests
import json

url = "https://easycms.fi/public_api/set_products_multi"
headers = {
    "Authorization1": "TOKEN",
    "Content-Type": "application/json"
}
payload = {
    'username': 'USERNAME', 
    'password': 'PASSWORD', 
    'account': 'ACCOUNT_ID',
    'pid': 12345,
    'location_id': 'loc_001',
    'code': 'LOC-SPECIFIC-BARCODE',
    'price': 25.990,
    'pricebuy': 15.500,
    'supplier_id': 10,
    'discount': 5.00,
    'stime': '11:00',
    'etime': '14:00',
    'days_list': 'mon,tue,wed,thu,fri',
    'row_num': 1
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
print(response.text)

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();
String jsonPayload = """
    {
        "username": "USERNAME",
        "password": "PASSWORD",
        "account": "ACCOUNT_ID",
        "pid": 12345,
        "location_id": "loc_001",
        "code": "LOC-SPECIFIC-BARCODE",
        "price": 25.990,
        "pricebuy": 15.500,
        "supplier_id": 10,
        "discount": 5.00,
        "stime": "11:00",
        "etime": "14:00",
        "days_list": "mon,tue,wed,thu,fri",
        "row_num": 1
    }
    """;
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://easycms.fi/public_api/set_products_multi"))
    .headers("Authorization1", "TOKEN", "Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
    .build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

const https = require('https');

const payload = JSON.stringify({
  username: 'USERNAME', 
  password: 'PASSWORD', 
  account: 'ACCOUNT_ID',
  pid: 12345,
  location_id: 'loc_001',
  code: 'LOC-SPECIFIC-BARCODE',
  price: 25.990,
  pricebuy: 15.500,
  supplier_id: 10,
  discount: 5.00,
  stime: '11:00',
  etime: '14:00',
  days_list: 'mon,tue,wed,thu,fri',
  row_num: 1
});

const options = {
  hostname: 'easycms.fi',
  path: '/public_api/set_products_multi',
  method: 'POST',
  headers: {
    'Authorization1': 'TOKEN',
    'Content-Type': 'application/json',
    'Content-Length': Buffer.byteLength(payload)
  }
};

const req = https.request(options, (res) => {
  let data = '';
  res.on('data', (chunk) => { data += chunk; });
  res.on('end', () => { console.log(data); });
});
req.on('error', (e) => { console.error(e); });
req.write(payload);
req.end();

import okhttp3.OkHttpClient
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody

fun main() {
    val client = OkHttpClient()

    val jsonPayload = """
        {
            "username": "USERNAME",
            "password": "PASSWORD",
            "account": "ACCOUNT_ID",
            "pid": 12345,
            "location_id": "loc_001",
            "code": "LOC-SPECIFIC-BARCODE",
            "price": 25.990,
            "pricebuy": 15.500,
            "supplier_id": 10,
            "discount": 5.00,
            "stime": "11:00",
            "etime": "14:00",
            "days_list": "mon,tue,wed,thu,fri",
            "row_num": 1
        }
    """.trimIndent()

    val requestBody = jsonPayload.toRequestBody("application/json".toMediaType())

    val request = Request.Builder()
        .url("https://easycms.fi/public_api/set_products_multi")
        .post(requestBody)
        .addHeader("Authorization1", "TOKEN")
        .build()

    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) throw IOException("Unexpected code $response")
        println(response.body?.string())
    }
}




Handling Endpoint Results

When you make a request to the endpoint, you receive a JSON response containing various keys and values. Here's an explanation of the response keys and their meanings:


{
  "success": true,
  "message": "Products multi record created successfully",
  "action": "create",
  "data": {
    "multi_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "pid": 12345,
    "location_id": "loc_001",
    "code": "LOC-SPECIFIC-BARCODE",
    "price": 25.99,
    "pricebuy": 15.5,
    "supplier_id": 10,
    "discount": 5.0,
    "row_num": 1,
    "stime": "11:00:00",
    "etime": "14:00:00",
    "days_list": "monday,tuesday,wednesday,thursday,friday"
  },
  "changes": {
    "code": {"old": null, "new": "LOC-SPECIFIC-BARCODE"},
    "price": {"old": null, "new": 25.99},
    "pricebuy": {"old": null, "new": 15.5},
    "supplier_id": {"old": null, "new": 10},
    "discount": {"old": null, "new": 5.0},
    "stime": {"old": null, "new": "11:00:00"},
    "etime": {"old": null, "new": "14:00:00"},
    "days_list": {"old": null, "new": "monday,tuesday,wednesday,thursday,friday"}
  }
}
    

{
  "success": true,
  "message": "Products multi record updated successfully",
  "action": "update",
  "data": {
    "multi_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "price": 29.99,
    "discount": 10.0,
    "etime": "16:00:00"
  },
  "changes": {
    "price": {"old": 25.99, "new": 29.99},
    "discount": {"old": 5.0, "new": 10.0},
    "etime": {"old": "14:00:00", "new": "16:00:00"}
  }
}
    

{
  "success": true,
  "message": "Products multi record deleted successfully",
  "action": "delete",
  "data": {
    "deleted_multi_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  },
  "changes": []
}
    

Error Handling

Here are the possible error messages and their meanings:

Error Code Description
MISSING_PID The pid parameter is required
MISSING_LOCATION_ID The location_id parameter is required (when no scheduling fields are provided)
PRODUCT_NOT_FOUND The product with the specified pid does not exist
LOCATION_NOT_FOUND The specified location does not exist
RECORD_NOT_FOUND No record found for deletion
NO_FIELDS_TO_UPDATE No valid fields provided for update
INVALID_TIME_FORMAT Invalid time format (expected HH:MM or HH:MM:SS)
INVALID_DATE_FORMAT Invalid date format (expected YYYY-MM-DD)
INVALID_DAYS_LIST Invalid day name in days_list
INVALID_BOOLEAN_VALUE A discount ladder flag had a non-boolean value (expected 1/0 or true/false)
UPDATE_FAILED Failed to update the record
INSERT_FAILED Failed to create the record
DELETE_FAILED Failed to delete the record
UN-AUTHORIZED Incorrect username or password
this_account_does_not_exist_or_your_credentials_do_not_match_this_account The account doesn't exist or mismatched credentials

Error Response Example

{
  "success": false,
  "message": "Product not found with pid: 99999",
  "error_code": "PRODUCT_NOT_FOUND",
  "action": "",
  "data": null,
  "changes": []
}

Use Cases

Setting Location-Specific Pricing

Use this endpoint when a product has different prices at different locations:

POST /set_products_multi
{
  "pid": 12345,
  "location_id": "warehouse_a",
  "price": 19.990,
  "pricebuy": 10.000
}

Setting Location-Specific Barcodes

When the same product has different barcodes at different locations:

POST /set_products_multi
{
  "pid": 12345,
  "location_id": "store_1",
  "code": "STORE1-SKU-123"
}

Lunch-Time Scheduled Pricing (Weekdays)

Set a different price for weekday lunch hours. The row_num controls priority:

POST /set_products_multi
{
  "pid": 12345,
  "location_id": "restaurant",
  "price": 12.990,
  "stime": "11:00",
  "etime": "14:00",
  "sdate": "2025-06-01",
  "edate": "2025-08-31",
  "days_list": "monday,tuesday,wednesday,thursday,friday",
  "row_num": 1
}

Scheduling Without Location (Global Scheduled Pricing)

Create a scheduled price override that applies regardless of location. Omit location_id when only providing scheduling fields:

POST /set_products_multi
{
  "pid": 12345,
  "price": 9.990,
  "stime": "15:00",
  "etime": "17:00",
  "days_list": "every_day",
  "row_num": 2
}

Multiple Priority Levels

Create multiple scheduled pricing rows with different priorities. Lower row_num = higher priority. The first matching schedule wins:

# Row 0: Base price for this location (always active, no scheduling)
POST /set_products_multi
{
  "pid": 12345,
  "location_id": "restaurant",
  "price": 19.990,
  "row_num": 0
}

# Row 1: Happy hour pricing (weekday afternoons)
POST /set_products_multi
{
  "pid": 12345,
  "location_id": "restaurant",
  "price": 14.990,
  "stime": "15:00",
  "etime": "17:00",
  "days_list": "mon,tue,wed,thu,fri",
  "row_num": 1
}

# Row 2: Weekend brunch pricing
POST /set_products_multi
{
  "pid": 12345,
  "location_id": "restaurant",
  "price": 16.990,
  "stime": "10:00",
  "etime": "13:00",
  "days_list": "saturday,sunday",
  "row_num": 2
}

Locking a Scheduled Price Against Quantity Tiers (3.89+)

A lunch discount that must not be beaten by the product's quantity tiers — the row's 15 % replaces the product's own discount and skips quantity tiers, while customer prices still win when bigger:

POST /set_products_multi
{
  "pid": 12345,
  "location_id": "restaurant",
  "discount": 15,
  "discount_lock": true,
  "stime": "11:00",
  "etime": "14:00",
  "days_list": "every_day",
  "row_num": 1
}

Removing Location Data

Delete a multi-location record:

POST /set_products_multi
{
  "pid": 12345,
  "location_id": "old_location",
  "delete": 1
}

Related Endpoints