This document outlines the procedure for making API calls to update product stock data via https://easycms.fi/public_api/set_products_stock/.
Update product stock quantities with full audit trail support.
https://easycms.fi/public_api/set_products_stock/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 update stock for |
location_id |
integer | Location ID - The warehouse/location |
shelf_id |
integer | Shelf ID - The shelf within the location |
stock |
decimal | Stock quantity change (positive for additions, negative for deductions) |
reason |
integer | Reason code for the stock movement (see Reason Codes table) |
| Parameter | Type | Description |
|---|---|---|
best_before_date |
date | Best before date (YYYY-MM-DD format) |
price |
decimal | Sell price at time of movement (for stockdiary reference) |
price_buy |
decimal | Buy price at time of movement (for stockdiary reference) |
supplier_id |
integer | Supplier ID (for stockdiary reference) |
reference |
string | Reference text (order ID, invoice ID, etc.) |
The following reason codes are supported for stock movements:
| Code | Constant | Description |
|---|---|---|
1 |
IN_PURCHASE | Stock received from purchase order |
2 |
IN_REFUND | Stock returned from customer refund |
3 |
IN_MOVEMENT | Stock moved in from another location |
4 |
IN_SALE_CORRECTION | Sale cancelled, stock returned |
| Code | Constant | Description |
|---|---|---|
-1 |
OUT_SALE | Stock sold to customer |
-2 |
OUT_REFUND | Stock refunded to supplier |
-3 |
OUT_BREAK | Stock broken/damaged |
-4 |
OUT_MOVEMENT | Stock moved to another location |
-5 |
OUT_SAMPLE | Stock used as sample |
-6 |
OUT_FREE | Stock given for free |
-7 |
OUT_USED | Stock used internally |
-8 |
OUT_SUBTRACT | Stock adjustment (deduction) |
Note: The stock parameter should be positive for additions and negative for deductions. The reason code helps categorize the movement for reporting purposes.
# Add stock from purchase (IN_PURCHASE = 1)
curl -X POST 'https://easycms.fi/public_api/set_products_stock' \
-H 'Authorization1: TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"username": "USERNAME",
"password": "PASSWORD",
"account": "ACCOUNT_ID",
"pid": 12345,
"location_id": 1,
"shelf_id": 5,
"stock": 100,
"reason": 1,
"best_before_date": "2025-12-31",
"price_buy": 15.50,
"supplier_id": 10,
"reference": "PO-2024-001"
}'
# Deduct stock from sale (OUT_SALE = -1)
curl -X POST 'https://easycms.fi/public_api/set_products_stock' \
-H 'Authorization1: TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"username": "USERNAME",
"password": "PASSWORD",
"account": "ACCOUNT_ID",
"pid": 12345,
"location_id": 1,
"shelf_id": 5,
"stock": -5,
"reason": -1,
"price": 25.00,
"reference": "ORD-2024-1234"
}'
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://easycms.fi/public_api/set_products_stock",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'username' => 'USERNAME',
'password' => 'PASSWORD',
'account' => 'ACCOUNT_ID',
'pid' => 12345,
'location_id' => 1,
'shelf_id' => 5,
'stock' => 100, // positive for addition
'reason' => 1, // IN_PURCHASE
'best_before_date' => '2025-12-31',
'price_buy' => 15.50,
'supplier_id' => 10,
'reference' => 'PO-2024-001'
]),
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_stock"
headers = {
"Authorization1": "TOKEN",
"Content-Type": "application/json"
}
payload = {
'username': 'USERNAME',
'password': 'PASSWORD',
'account': 'ACCOUNT_ID',
'pid': 12345,
'location_id': 1,
'shelf_id': 5,
'stock': 100, # positive for addition
'reason': 1, # IN_PURCHASE
'best_before_date': '2025-12-31',
'price_buy': 15.50,
'supplier_id': 10,
'reference': 'PO-2024-001'
}
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": 1,
"shelf_id": 5,
"stock": 100,
"reason": 1,
"best_before_date": "2025-12-31",
"price_buy": 15.50,
"supplier_id": 10,
"reference": "PO-2024-001"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://easycms.fi/public_api/set_products_stock"))
.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: 1,
shelf_id: 5,
stock: 100,
reason: 1,
best_before_date: '2025-12-31',
price_buy: 15.50,
supplier_id: 10,
reference: 'PO-2024-001'
});
const options = {
hostname: 'easycms.fi',
path: '/public_api/set_products_stock',
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": 1,
"shelf_id": 5,
"stock": 100,
"reason": 1,
"best_before_date": "2025-12-31",
"price_buy": 15.50,
"supplier_id": 10,
"reference": "PO-2024-001"
}
""".trimIndent()
val requestBody = jsonPayload.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("https://easycms.fi/public_api/set_products_stock")
.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:
{
"status": "success",
"message": "Stock updated successfully",
"data": {
"pid": 12345,
"location_id": 1,
"shelf_id": 5,
"previous_stock": 100.0,
"stock_change": 50.0,
"new_stock": 150.0,
"reason": 1,
"reason_text": "IN_PURCHASE",
"stockdiary_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"best_before_date": "2025-12-31"
}
}
- `status`: The operation status ("success" or "error")
- `message`: Human-readable message describing the result
- `data`: Object containing the operation details:
- `pid`: The product ID that was updated
- `location_id`: The location ID
- `shelf_id`: The shelf ID
- `previous_stock`: The stock quantity before this update
- `stock_change`: The amount that was added (positive) or deducted (negative)
- `new_stock`: The current stock quantity after this update
- `reason`: The numeric reason code used
- `reason_text`: The human-readable reason text
- `stockdiary_id`: The UUID of the created stockdiary entry
- `best_before_date`: The best before date (if provided)
When creating a new stock entry (no previous stock existed):
{
"status": "success",
"message": "Stock entry created successfully",
"data": {
"pid": 12345,
"location_id": 2,
"shelf_id": 3,
"previous_stock": 0,
"stock_change": 100.0,
"new_stock": 100.0,
"reason": 1,
"reason_text": "IN_PURCHASE",
"stockdiary_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"best_before_date": "2025-12-31",
"is_new_entry": true
}
}
Here are the possible error messages and their meanings:
| Error Code | HTTP Status | Description |
|---|---|---|
MISSING_PID |
400 | The pid parameter is required |
MISSING_LOCATION_ID |
400 | The location_id parameter is required |
MISSING_SHELF_ID |
400 | The shelf_id parameter is required |
MISSING_STOCK |
400 | The stock parameter is required |
MISSING_REASON |
400 | The reason parameter is required |
INVALID_PID |
400 | The pid must be a valid integer |
INVALID_LOCATION_ID |
400 | The location_id must be a valid integer |
INVALID_SHELF_ID |
400 | The shelf_id must be a valid integer |
INVALID_STOCK |
400 | The stock must be a valid number |
INVALID_REASON |
400 | The reason code is not valid |
PRODUCT_NOT_FOUND |
404 | The product with the specified pid does not exist |
LOCATION_NOT_FOUND |
404 | The specified location does not exist |
SHELF_NOT_FOUND |
404 | The specified shelf does not exist |
STOCK_ENTRY_NOT_FOUND |
400 | No stock entry exists for this location/shelf (for deductions) |
INSUFFICIENT_STOCK |
400 | Not enough stock to complete the deduction |
CANNOT_DEDUCT_FROM_NONEXISTENT |
400 | Cannot deduct stock from a location that has no stock |
UN-AUTHORIZED |
401 | Incorrect username or password |
this_account_does_not_exist_or_your_credentials_do_not_match_this_account |
401 | The account doesn't exist or mismatched credentials |
{
"status": "error",
"error_code": "INSUFFICIENT_STOCK",
"message": "Insufficient stock. Current stock: 50, Requested deduction: 100"
}
Every stock modification creates an entry in tbl_stockdiary with:
pid: Product IDlocation_id: Location IDshelf_id: Shelf IDstock: Quantity change (positive or negative)reason: Reason codedatenew: Current timestampprice: Sell price (if provided)price_buy: Buy price (if provided)supplier_id: Supplier ID (if provided)appuser: API user referencebest_before_date: Best before date (if provided)get_products_stock to verify available stockINSUFFICIENT_STOCK errorbest_before_date for perishable itemsprice and price_buy for accurate stock valuation reportsPOST /set_products_stock
{
"pid": 12345,
"location_id": 1,
"shelf_id": 5,
"stock": 100,
"reason": 1,
"price_buy": 15.50,
"supplier_id": 10,
"reference": "PO-2024-001"
}
POST /set_products_stock
{
"pid": 12345,
"location_id": 1,
"shelf_id": 5,
"stock": -5,
"reason": -1,
"price": 25.00,
"reference": "ORD-2024-1234"
}
POST /set_products_stock
{
"pid": 12345,
"location_id": 1,
"shelf_id": 5,
"stock": -10,
"reason": -3,
"reference": "DAMAGE-REPORT-2024-056"
}
First, deduct from source location:
POST /set_products_stock
{
"pid": 12345,
"location_id": 1,
"shelf_id": 5,
"stock": -50,
"reason": -4,
"reference": "TRANSFER-2024-001-OUT"
}
Then, add to destination location:
POST /set_products_stock
{
"pid": 12345,
"location_id": 2,
"shelf_id": 3,
"stock": 50,
"reason": 3,
"reference": "TRANSFER-2024-001-IN"
}