This document provides guidance on using the API to create or update purchase orders in the API server's database through the set_purchase_order endpoint. This involves two steps: API authentication and purchase order creation/update.
This API call is essential for creating new purchase orders or updating existing ones in the system.
To create or update a purchase order, follow the steps below using the set_purchase_order API call. The process involves providing credentials for API login and specific details for the purchase order.
https://easycms.fi/public_api/set_purchase_order/For purchase order operations, you need to provide two sets of parameters - one for API authentication and the other for the purchase order details.
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.supplier_id: Supplier ID (Integer) - Required. The ID of the supplier for this purchase order.order_date: Order Date (String) - The date of the order placement. Defaults to current date if not provided.delivery_date: Delivery Date (String) - The expected delivery date.status: Status (Integer) - The status of the purchase order. Defaults to 1 (draft) if not provided.purchase_order_lines: Array of purchase order line data - Required for creation.id: Purchase Order ID (Integer) - Required for update. The ID of the purchase order to update.purchase_order_lines: Array of purchase order line data (optional for update).id: Purchase Order ID (Integer) - The unique identifier for the purchase order (for updates only).reference: Reference Number (Integer) - The reference number for the purchase order (auto-generated for new orders).supplier_id: Supplier ID (Integer) - The ID of the supplier for this purchase order.title: Title (String) - The title of the purchase order.note: Note (String) - General notes for the order.admin_note: Admin Note (String) - Administrative notes.delivery_instructions: Delivery Instructions (String) - Special delivery instructions.order_personnel_name: Order Personnel Name (String) - Name of the person who placed the order.loading_personnel_name: Loading Personnel Name (String) - Name of the loading personnel.delivery_personnel_name: Delivery Personnel Name (String) - Name of the delivery personnel.recipient_personnel_name: Recipient Personnel Name (String) - Name of the recipient.payer_details: Payer Details (String) - Information about the payer.payee_details: Payee Details (String) - Information about the payee.reason: Reason (String) - The reason for the purchase order.total: Total (Float) - The total amount of the purchase order.discount: Discount (Float) - Any discount applied to the order.status: Status (Integer) - The status of the purchase order:
1 = Draft100 = Sent to supplier110 = Preparing120 = Shipped200 = Received-1 = CancelledEach item in the purchase_order_lines array can contain:
| Field | Type | Required | Description |
|---|---|---|---|
pid |
Integer | Yes | Product ID |
quantity |
Float | Yes | Ordered quantity. Read-only when status = 200 (received) |
unit_price |
Float | Yes | Purchase price per unit |
received_quantity |
Float | No | Actually received quantity. Only applicable when status = 200 |
arrival_date |
String | No | Arrival date of goods (format: YYYY-MM-DD) |
product_name |
String | No | Product name override |
line_note |
String | No | Notes specific to this line |
best_before_date or bbd |
String | No | Best before date. Accepted formats: YYYY-MM-DD (recommended), DD.MM.YYYY, MM/DD/YYYY, ISO-8601. Normalized to a date literal before storing (v3.16). Send it before confirming the line — the confirm step copies the line's bbd into the stock records |
barcode |
String | No | Product barcode |
boxcode |
String | No | Box code |
location_id |
String | No | Storage location ID |
shelf_id |
Integer | No | Shelf identifier |
discount |
Float | No | Discount percentage |
The API enforces business rules matching the admin CMS behavior for quantity fields:
| Order Status | quantity (Ordered) |
received_quantity (Received) |
|---|---|---|
| Draft (1), Sent (100), Preparing (110), Shipped (120) | Editable - set the qty you plan to order | Ignored (set to 0) |
| Received (200) | READ-ONLY - API returns error if you try to change it | Editable - set the actual qty received |
| Cancelled (-1) | Read-only | Read-only |
Important: When updating lines on a received order (status = 200):
quantity value (it must match the existing ordered qty)received_quantity to reflect what was actually receivedquantity on a received order will result in an errorConfirmed-line safety rules (v3.16+): re-sending purchase_order_lines on a received (status 200) order follows these rules, so duplicate/idempotent submissions are safe:
lines_skipped_confirmed). The line's confirmed stock is never touched by a re-send.received_quantity → the previous confirmed stock movement is reversed, the line is un-confirmed and staged with the new quantity — call set_purchase_order_confirm again to apply the corrected amount.set_purchase_order_confirm); send only the lines you are still working on.When a purchase order's status is changed to 200 (received):
received_quantity = quantity (pre-filled)set_purchase_order_confirm to move stockset_purchase_order_confirm without purchase_line_id to confirm all lines at onceThis ensures accurate stock tracking -- the person receiving goods explicitly confirms what was actually received.
# Create a new purchase order (draft)
curl -X POST 'https://easycms.fi/public_api/set_purchase_order' \
-H 'Authorization1: TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"username": "USERNAME",
"password": "PASSWORD",
"account": "ACCOUNT_ID",
"supplier_id": 5,
"order_date": "2024-01-15 10:30:00",
"status": 1,
"title": "Monthly Restock Order",
"note": "Regular monthly order",
"delivery_instructions": "Deliver to warehouse B",
"purchase_order_lines": [
{
"pid": 12345,
"quantity": 100,
"unit_price": 15.50,
"product_name": "Product Name",
"barcode": "1234567890123",
"bbd": "2025-12-31"
},
{
"pid": 12346,
"quantity": 50,
"unit_price": 22.00,
"product_name": "Another Product",
"boxcode": "BOX001"
}
]
}'
# Update a received order with received_quantity
# Note: quantity must match the original ordered qty (read-only when received)
curl -X POST 'https://easycms.fi/public_api/set_purchase_order' \
-H 'Authorization1: TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"username": "USERNAME",
"password": "PASSWORD",
"account": "ACCOUNT_ID",
"id": 123,
"status": 200,
"note": "Order received and verified",
"purchase_order_lines": [
{
"pid": 12345,
"quantity": 100,
"unit_price": 15.50,
"received_quantity": 95,
"arrival_date": "2024-01-19"
}
]
}'
# Update order header only (no lines)
curl -X POST 'https://easycms.fi/public_api/set_purchase_order' \
-H 'Authorization1: TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"username": "USERNAME",
"password": "PASSWORD",
"account": "ACCOUNT_ID",
"id": 123,
"status": 200,
"note": "Order received and verified"
}'
$curl = curl_init();
$payload = [
'username' => 'USERNAME',
'password' => 'PASSWORD',
'account' => 'ACCOUNT_ID',
'supplier_id' => 5,
'order_date' => '2024-01-15 10:30:00',
'status' => 1,
'title' => 'Monthly Restock Order',
'note' => 'Regular monthly order',
'delivery_instructions' => 'Deliver to warehouse B',
'purchase_order_lines' => [
[
'pid' => 12345,
'quantity' => 100,
'unit_price' => 15.50,
'product_name' => 'Product Name',
'barcode' => '1234567890123',
'bbd' => '2025-12-31'
],
[
'pid' => 12346,
'quantity' => 50,
'unit_price' => 22.00,
'product_name' => 'Another Product',
'boxcode' => 'BOX001'
]
]
];
curl_setopt_array($curl, [
CURLOPT_URL => "https://easycms.fi/public_api/set_purchase_order",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
"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_purchase_order"
headers = {
"Authorization1": "TOKEN",
"Content-Type": "application/json"
}
# Create a new purchase order
payload = {
"username": "USERNAME",
"password": "PASSWORD",
"account": "ACCOUNT_ID",
"supplier_id": 5,
"order_date": "2024-01-15 10:30:00",
"status": 1,
"title": "Monthly Restock Order",
"note": "Regular monthly order",
"delivery_instructions": "Deliver to warehouse B",
"purchase_order_lines": [
{
"pid": 12345,
"quantity": 100,
"unit_price": 15.50,
"product_name": "Product Name",
"barcode": "1234567890123",
"bbd": "2025-12-31"
},
{
"pid": 12346,
"quantity": 50,
"unit_price": 22.00,
"product_name": "Another Product",
"boxcode": "BOX001"
}
]
}
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;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
String jsonPayload = """
{
"username": "USERNAME",
"password": "PASSWORD",
"account": "ACCOUNT_ID",
"supplier_id": 5,
"order_date": "2024-01-15 10:30:00",
"status": 1,
"title": "Monthly Restock Order",
"note": "Regular monthly order",
"purchase_order_lines": [
{
"pid": 12345,
"quantity": 100,
"unit_price": 15.50,
"barcode": "1234567890123"
}
]
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://easycms.fi/public_api/set_purchase_order"))
.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 = {
username: 'USERNAME',
password: 'PASSWORD',
account: 'ACCOUNT_ID',
supplier_id: 5,
order_date: '2024-01-15 10:30:00',
status: 1,
title: 'Monthly Restock Order',
note: 'Regular monthly order',
purchase_order_lines: [
{
pid: 12345,
quantity: 100,
unit_price: 15.50,
product_name: 'Product Name',
barcode: '1234567890123',
bbd: '2025-12-31'
}
]
};
const data = JSON.stringify(payload);
const options = {
hostname: 'easycms.fi',
path: '/public_api/set_purchase_order',
method: 'POST',
headers: {
'Authorization1': 'TOKEN',
'Content-Type': 'application/json',
'Content-Length': data.length
}
};
const req = https.request(options, (res) => {
let responseData = '';
res.on('data', (chunk) => { responseData += chunk; });
res.on('end', () => { console.log(responseData); });
});
req.on('error', (e) => { console.error(e); });
req.write(data);
req.end();
import React, { useEffect, useState } from 'react';
function App() {
const [responseData, setResponseData] = useState('');
useEffect(() => {
const createPurchaseOrder = async () => {
try {
const response = await fetch('https://easycms.fi/public_api/set_purchase_order', {
method: 'POST',
headers: {
'Authorization1': 'TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'USERNAME',
password: 'PASSWORD',
account: 'ACCOUNT_ID',
supplier_id: 5,
order_date: '2024-01-15 10:30:00',
status: 1,
title: 'Monthly Restock Order',
note: 'Regular monthly order',
purchase_order_lines: [
{
pid: 12345,
quantity: 100,
unit_price: 15.50,
barcode: '1234567890123'
}
]
})
});
const data = await response.json();
setResponseData(JSON.stringify(data, null, 2));
} catch (error) {
console.error(error);
}
};
createPurchaseOrder();
}, []);
return <pre>{responseData}</pre>;
}
export default App;
import okhttp3.OkHttpClient
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException
fun main() {
val client = OkHttpClient()
val jsonPayload = """
{
"username": "USERNAME",
"password": "PASSWORD",
"account": "ACCOUNT_ID",
"supplier_id": 5,
"order_date": "2024-01-15 10:30:00",
"status": 1,
"title": "Monthly Restock Order",
"note": "Regular monthly order",
"purchase_order_lines": [
{
"pid": 12345,
"quantity": 100,
"unit_price": 15.50,
"barcode": "1234567890123"
}
]
}
""".trimIndent()
val requestBody = jsonPayload.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("https://easycms.fi/public_api/set_purchase_order")
.post(requestBody)
.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.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Authorization1", "TOKEN");
var payload = new
{
username = "USERNAME",
password = "PASSWORD",
account = "ACCOUNT_ID",
supplier_id = 5,
order_date = "2024-01-15 10:30:00",
status = 1,
title = "Monthly Restock Order",
note = "Regular monthly order",
purchase_order_lines = new[]
{
new
{
pid = 12345,
quantity = 100,
unit_price = 15.50,
barcode = "1234567890123"
}
}
};
var jsonContent = new StringContent(
JsonSerializer.Serialize(payload),
Encoding.UTF8,
"application/json"
);
var response = await httpClient.PostAsync(
"https://easycms.fi/public_api/set_purchase_order",
jsonContent
);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
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:
Response Structure:
- `status`: Response status - "success" or "error"
- `message`: Human-readable message describing the result
- `data`: Object containing the created/updated order data
- `id`: The purchase order ID
- `order`: The complete order object with all fields
- `changes`: Object tracking what was changed
- For CREATE:
- `order_created`: Object with new order details (id, reference, supplier_id)
- `order_lines`: Result of line processing
- For UPDATE:
- `order`: Object with field-level changes (old and new values)
- `order_lines`: Result of line processing (if lines were provided)
Order Object Fields:
- `id`: Purchase Order ID - Unique identifier
- `reference`: Reference Number - Auto-generated reference
- `supplier_id`: Supplier ID - The supplier for this order
- `order_date`: Order Date - When the order was placed
- `status`: Status Code - Current order status
- `title`: Title - Order title
- `note`: Note - General notes
- `admin_note`: Admin Note - Administrative notes
- `delivery_instructions`: Delivery Instructions
- `total`: Total - Order total amount
- `discount`: Discount - Applied discount
- `payer_details`: Payer Details
- `payee_details`: Payee Details
- `create_date`: Create Date - When record was created
- `update_date`: Update Date - When record was last updated
Order Lines Result:
- `success`: Boolean - Whether line processing succeeded (true when at least one line was processed OR skipped as already-confirmed)
- `lines_processed`: Integer - Number of lines successfully processed
- `lines_skipped`: Integer - Number of lines skipped due to errors
- `lines_skipped_confirmed`: Integer - Number of already-confirmed lines skipped (idempotent re-send, no changes made)
- `errors`: Array of Strings - Error descriptions for skipped lines
- `notes`: Array of Strings - Per-line informational notes (e.g. confirmed quantity reversed and un-confirmed)
- `lines`: Array of Objects - Per-line breakdown (one entry per processed line)
- `index`: The line's position in the request array
- `pid`: Product ID of the line
- `purchase_line_id`: The line's `purchase_line_id` — use this to reference the line in `set_purchase_order_line` (inline editing) or in later `purchase_order_lines` updates
- `action`: `"added"` (new line created) or `"updated"` (existing line updated)
- `number`: Line number assigned within the order
- `message`: String - Summary of the result
{
"status": "success",
"message": "Purchase order created successfully",
"data": {
"id": 124,
"order": {
"id": "124",
"reference": "2024001",
"supplier_id": "5",
"order_date": "2024-01-15 10:30:00",
"status": "1",
"title": "Monthly Restock Order",
"note": "Regular monthly order",
"admin_note": "",
"delivery_instructions": "Deliver to warehouse B",
"total": "0.0000",
"discount": "0.0000",
"create_date": "2024-01-15 10:30:00",
"update_date": "2024-01-15 10:30:00",
"deleted": "0"
}
},
"changes": {
"order_created": {
"id": 124,
"reference": "2024001",
"supplier_id": 5
},
"order_lines": {
"success": true,
"lines_processed": 2,
"lines_skipped": 0,
"errors": [],
"lines": [
{ "index": 0, "pid": 101, "purchase_line_id": 551, "action": "added", "number": 1 },
{ "index": 1, "pid": 102, "purchase_line_id": 552, "action": "added", "number": 2 }
],
"message": "Purchase order lines updated for 2 product(s)"
}
}
}
// Example: Marking order as received with actual received quantities
// POST with id=123, status=200, and purchase_order_lines with received_quantity
{
"status": "success",
"message": "Purchase order updated successfully",
"data": {
"id": 123,
"order": {
"id": "123",
"reference": "2024000",
"supplier_id": "5",
"order_date": "2024-01-14 09:00:00",
"status": "200",
"title": "Monthly Restock Order",
"note": "Order received and verified",
"total": "1550.0000",
"discount": "0.0000",
"create_date": "2024-01-14 09:00:00",
"update_date": "2024-01-15 14:30:00",
"deleted": "0"
}
},
"changes": {
"order": {
"status": {
"old": "120",
"new": 200
},
"note": {
"old": "Regular monthly order",
"new": "Order received and verified"
}
},
"order_lines": {
"success": true,
"lines_processed": 2,
"lines_skipped": 0,
"errors": [],
"lines": [
{ "index": 0, "pid": 101, "purchase_line_id": 551, "action": "added", "number": 1 },
{ "index": 1, "pid": 102, "purchase_line_id": 552, "action": "added", "number": 2 }
],
"message": "Purchase order lines updated for 2 product(s)"
}
}
}
// Example: Attempting to change ordered quantity on a received order
// The API blocks this because quantity is read-only when status=200
{
"status": "success",
"message": "Purchase order updated successfully",
"data": {
"id": 123,
"order": { ... }
},
"changes": {
"order": {
"note": {
"old": "Old note",
"new": "Updated note"
}
},
"order_lines": {
"success": true,
"lines_processed": 1,
"lines_skipped": 1,
"errors": [
"Line 1: Cannot change ordered quantity (quantity) for PID 12345 - order status is 'received' (200). Ordered quantity is read-only once received. Use received_quantity to update received amounts."
],
"message": "Purchase order lines updated for 1 product(s). Skipped 1 line(s)."
}
}
}
Purchase orders follow a status flow from creation to completion:
| Status Code | Title | Description |
|---|---|---|
| 1 | draft_order | Order is in draft state, can be modified freely |
| 100 | order_sent_to_supplier | Order has been sent to supplier |
| 110 | supplier_preparing_for_shipment | Supplier is preparing the order |
| 120 | supplier_shipped | Order has been shipped by supplier |
| 200 | order_received | Order received - quantity becomes read-only, received_quantity is editable |
| -1 | cancelled | Order has been cancelled |
Important status rules:
200 (received), the quantity field on each line becomes read-onlyreceived_quantity field is used to record the actual amount received per linereceived_quantity for totals calculation when status is 200Here 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.METHOD_NOT_ALLOWED: Only POST method is allowed for this endpoint.MISSING_REQUIRED_FIELDS: Missing required fields for creation. Required: supplier_idMISSING_ORDER_LINES: Purchase order lines are required for creating a purchase order.ORDER_NOT_FOUND: The specified purchase order ID was not found (for updates).UPDATE_FAILED: Failed to update the purchase order in the database.CREATE_FAILED: Failed to create the purchase order in the database.Line-level errors (returned in changes.order_lines.errors array):
Cannot change ordered quantity (quantity) for PID {pid} - order status is 'received' (200): You attempted to change the ordered quantity on a received order. Ordered qty is read-only once received. Use received_quantity instead.Missing required fields (pid, quantity, unit_price): A line item is missing one of the required fields.Product PID {pid} not found: The specified product ID does not exist.supplier_id and purchase_order_lines when creating a new order.received_quantity for each line to record actual amounts. The quantity field must remain unchanged.pid, quantity, and unit_price at minimum.quantity for the amount you ordered (editable before receiving)received_quantity for the amount you actually received (editable only after receiving)bbd (or best_before_date), barcode, boxcode, and arrival_date to capture additional product information during receiving.set_purchase_order_confirm), stop including it in subsequent purchase_order_lines arrays. It is ignored (idempotent skip), but sending only pending lines keeps responses clean and avoids ambiguity.