This document outlines the procedure for making API calls to manage product multi-location data via https://easycms.fi/public_api/set_products_multi/.
row_num) — lower numbers are evaluated first for scheduled pricingrow_num order and the first matching schedule winsprice is the net sell price (3 decimals) — the same value the POS stores; gross = net × (1 + VAT/100)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+)Products can have time-based price overrides that activate during specific time windows. A scheduled pricing row defines:
stime / etime (e.g., lunch pricing 11:00–14:00)sdate / edate (e.g., summer promotion June–August)days_list (e.g., weekdays only: monday–friday)row_num — when multiple schedules could match, the row with the lowest row_num winsIf 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 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.
https://easycms.fi/public_api/set_products_multi/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. | Parameter | Type | Description |
|---|---|---|
pid |
integer | Product ID - The product to manage multi-location data for |
| 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. |
| 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. |
| 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. |
| 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
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_lockadditionally blocks quantity tiers (never customer pricing). Rows resolve inrow_numorder; 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 carriesflag_fields_ignored: truewith a note inmessage— all other fields still save normally.
| Parameter | Type | Description |
|---|---|---|
delete |
integer | Set to 1 to delete the record (requires multi_id or pid+location_id) |
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.
When an existing record is found (by multi_id or pid+location_id), it is updated with the provided fields.
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.
# 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())
}
}
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": []
}
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 |
{
"success": false,
"message": "Product not found with pid: 99999",
"error_code": "PRODUCT_NOT_FOUND",
"action": "",
"data": null,
"changes": []
}
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
}
When the same product has different barcodes at different locations:
POST /set_products_multi
{
"pid": 12345,
"location_id": "store_1",
"code": "STORE1-SKU-123"
}
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
}
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
}
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
}
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
}
Delete a multi-location record:
POST /set_products_multi
{
"pid": 12345,
"location_id": "old_location",
"delete": 1
}