This document outlines the procedure for making API calls to retrieve product multi-location data from https://easycms.fi/public_api/get_products_multi/. This includes location-specific pricing, barcodes, supplier info, and time-based scheduled pricing data.
Fetch multi-location product data effectively using the below API call. Apply different parameters to filter results according to your specific needs.
Each record in the response may include scheduled pricing fields. A record with scheduling fields is only active when the current time/date/day matches the defined schedule:
stime / etime: Time window (e.g., "11:00:00" to "14:00:00")sdate / edate: Date range (e.g., "2025-06-01" to "2025-08-31")days_list: Comma-separated day names (e.g., "monday,tuesday,wednesday,thursday,friday")row_num: Priority order — lower numbers are evaluated first. The first matching schedule wins.If all scheduling fields are NULL, the row is always active (permanent override).
Each record also carries three per-row flags that steer how the row's discount competes with the product's own discount, quantity discount tiers and Buy-X-Get-X campaigns:
discount_lock: 1/0 — while this row wins the price resolution, its discount replaces the product's own discount and quantity discount tiers are skipped. Customer-specific prices and VIP discounts still apply (the bigger discount wins). The lock has no effect when the row carries no discount.ignore_qty_discount: 1/0 — quantity discount tiers are skipped on lines priced from this row.ignore_bxgx: 1/0 — Buy-X-Get-X free units are not generated on lines priced from this row.Rows within the same product resolve by row_num (later rows win the fields they set); a row carrying a location and/or its own barcode/boxcode is specific — its discount replaces the product's general discount even when smaller. The flags are returned on accounts running platform version 3.89 or newer; older accounts simply omit them. They can be managed via Set Products Multi.
https://easycms.fi/public_api/get_products_multi/Each parameter can be used individually or in combination to refine your data retrieval:
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. start - Specify the starting point of the row from which to begin fetching records.limit - Control the number of records returned in a single request. The maximum limit is 50, but you can opt for a smaller number based on your needs.
To manage data effectively, this endpoint supports fetching up to 50 multi-product records at a time. Each filter parameter is optional and designed to refine your search.
pid: To filter by a single product ID. The value needs to be an integer.supplier_id: To filter by supplier ID. The value needs to be an integer.location_id: To filter by location ID. The value needs to be a string.
# Get all multi-location records
curl -X POST 'https://easycms.fi/public_api/get_products_multi' \
-H 'Authorization1: TOKEN' \
-d 'username=USERNAME&password=PASSWORD&account=ACCOUNT_ID'
# Get multi-location records for a specific product
curl -X POST 'https://easycms.fi/public_api/get_products_multi' \
-H 'Authorization1: TOKEN' \
-d 'username=USERNAME&password=PASSWORD&account=ACCOUNT_ID&pid=12345'
# Get multi-location records for a specific location
curl -X POST 'https://easycms.fi/public_api/get_products_multi' \
-H 'Authorization1: TOKEN' \
-d 'username=USERNAME&password=PASSWORD&account=ACCOUNT_ID&location_id=loc_001'
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://easycms.fi/public_api/get_products_multi",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'username' => 'USERNAME',
'password' => 'PASSWORD',
'account' => 'ACCOUNT_ID',
'pid' => 12345
]),
CURLOPT_HTTPHEADER => array("Authorization1: TOKEN"),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
import requests
url = "https://easycms.fi/public_api/get_products_multi"
headers = {"Authorization1": "TOKEN"}
payload = {'username': 'USERNAME', 'password': 'PASSWORD', 'account': 'ACCOUNT_ID', 'pid': 12345}
response = requests.post(url, headers=headers, data=payload)
print(response.text)
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://easycms.fi/public_api/get_products_multi"))
.headers("Authorization1", "TOKEN")
.POST(HttpRequest.BodyPublishers.ofString("username=USERNAME&password=PASSWORD&account=ACCOUNT_ID&pid=12345"))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
const https = require('https');
const data = new URLSearchParams({
username: 'USERNAME',
password: 'PASSWORD',
account: 'ACCOUNT_ID',
pid: '12345'
}).toString();
const options = {
hostname: 'easycms.fi',
path: '/public_api/get_products_multi',
method: 'POST',
headers: {
'Authorization1': 'TOKEN',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': data.length
}
};
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(data);
req.end();
import React, { useEffect, useState } from 'react';
function App() {
const [productMultiData, setProductMultiData] = useState('');
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://easycms.fi/public_api/get_products_multi', {
method: 'POST',
headers: {'Authorization1': 'TOKEN', 'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({username: 'USERNAME', password: 'PASSWORD', account: 'ACCOUNT_ID', pid: '12345'}).toString()
});
const data = await response.text();
setProductMultiData(data);
} catch (error) {
console.error(error);
}
};
fetchData();
}, []);
return (<div>{productMultiData}</div>);
}
export default App;
import okhttp3.OkHttpClient
import okhttp3.FormBody
import okhttp3.Request
fun main() {
val client = OkHttpClient()
val formBody = FormBody.Builder()
.add("username", "USERNAME")
.add("password", "PASSWORD")
.add("account", "ACCOUNT_ID")
.add("pid", "12345")
.build()
val request = Request.Builder()
.url("https://easycms.fi/public_api/get_products_multi")
.post(formBody)
.addHeader("Authorization1", "TOKEN")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println(response.body?.string())
}
}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var token = "TOKEN";
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("username", "USERNAME"),
new KeyValuePair<string, string>("password", "PASSWORD"),
new KeyValuePair<string, string>("account", "ACCOUNT_ID"),
new KeyValuePair<string, string>("pid", "12345")
});
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization1", token);
var response = await httpClient.PostAsync("https://easycms.fi/public_api/get_products_multi", content);
if (response.IsSuccessStatusCode)
{
var responseData = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseData);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
}
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:
- `start`: Starting index for the data. Set to 0 for the first page.
- `limit`: Maximum number of records returned (up to 50).
- `count`: Number of records in this response.
- `total_count`: Total records matching the filters.
- `WHERE`: (Optional) Filter conditions applied to the query.
- `multi_id`: Unique record UUID (string) - Can be used with set_products_multi to target specific records for update or deletion.
- `pid`: Product ID (integer) - The product this record belongs to.
- `crp_id`: Currency profile ID (string) - Links to currency settings.
- `code`: Barcode/SKU (string) - Location-specific barcode for this product.
- `boxcode`: Box code (string) - Box code for this location.
- `supplier_id`: Supplier ID (integer) - Supplier for this location.
- `crsupplier_id`: Cash Register Supplier ID (string).
- `pricebuy`: Buy price (decimal) - Purchase price at this location.
- `price`: Sell price (decimal) - Net selling price at this location.
- `discount`: Discount percentage (decimal).
- `location_id`: Location ID (string) - The location this record applies to.
- `row_num`: Priority order (integer) - Lower = higher priority. First matching row wins for scheduled pricing.
**Scheduled Pricing Fields:**
- `stime`: Start time (time) - Start of time window (e.g., "11:00:00"). NULL = no time restriction.
- `etime`: End time (time) - End of time window (e.g., "14:00:00"). NULL = no time restriction.
- `sdate`: Start date (date) - Start of date range (e.g., "2025-06-01"). NULL = no start date.
- `edate`: End date (date) - End of date range (e.g., "2025-08-31"). NULL = no end date.
- `days_list`: Day names (string) - Comma-separated day names (e.g., "monday,tuesday,wednesday,thursday,friday"). NULL = no day restriction.
**Discount Ladder Flags (platform version 3.89+):**
- `discount_lock`: Lock discount (integer, 1/0) - While this row wins the price resolution, its discount replaces the product's own discount and quantity tiers are skipped (customer pricing/VIP still compete). Inert when the row has no discount.
- `ignore_qty_discount`: Ignore quantity discounts (integer, 1/0) - Quantity discount tiers are skipped on lines priced from this row.
- `ignore_bxgx`: Ignore Buy-X-Get-X (integer, 1/0) - BXGX free units are suppressed on lines priced from this row.
{
"INFO": {
"start": 0,
"limit": 50,
"count": 2,
"total_count": 2,
"WHERE": "pid = 12345"
},
"OUTPUT": [
{
"multi_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"pid": "12345",
"crp_id": "abc123",
"code": "LOC-SPECIFIC-BARCODE",
"boxcode": "BOX-001",
"supplier_id": "10",
"crsupplier_id": null,
"pricebuy": 15.5,
"row_num": 0,
"price": 19.99,
"discount": 0,
"discount_lock": 0,
"ignore_qty_discount": 0,
"ignore_bxgx": 0,
"location_id": "loc_001",
"stime": null,
"etime": null,
"sdate": null,
"edate": null,
"days_list": null
},
{
"multi_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"pid": "12345",
"crp_id": "abc123",
"code": null,
"boxcode": null,
"supplier_id": "0",
"crsupplier_id": null,
"pricebuy": null,
"row_num": 1,
"price": 12.99,
"discount": 15,
"discount_lock": 1,
"ignore_qty_discount": 1,
"ignore_bxgx": 0,
"location_id": "loc_001",
"stime": "11:00:00",
"etime": "14:00:00",
"sdate": "2025-06-01",
"edate": "2025-08-31",
"days_list": "monday,tuesday,wednesday,thursday,friday"
}
],
"success": true,
"message": "Data returned successfully"
}
Here are the possible error messages and their meanings:
UN-AUTHORIZED - _user_name_password_is_set_but_wrong_value!: 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.UN-AUTHORIZED - header is set but the header value is not correct!: Incorrect authorization header value.maximum_query_size_is_50_rows_per_query: Exceeded maximum limit of 50 rows per query.pid_must_be_a_valid_positive_integer: Invalid pid parameter value.supplier_id_must_be_a_valid_positive_integer: Invalid supplier_id parameter value.error_retrieving_product_multi_data: A database error occurred while retrieving data.NOT AUTHORIZED!: Authentication failed - no valid credentials provided.