Contents
On this page
Create Transfer - Plugixa Inventory
POST /plugixa-inventory/v1/transfers
Create a draft transfer that moves stock from one warehouse to another.
The user needs the plugixa_inventory_create capability (administrators have every inventory capability). With Pro Roles and permissions enforcing, their role must also allow creating transfers. Needs Plugixa Inventory Pro with the Warehouse transfers app on in Plugixa Inventory -> App Manager; with it off, this route answers 404.
Request
POST https://your-site.example/wp-json/plugixa-inventory/v1/transfers
Request body
Content type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
from_warehouse_id |
integer |
Yes | The live warehouse the stock leaves. |
to_warehouse_id |
integer |
Yes | The live warehouse the stock arrives in. Not the same as from_warehouse_id. |
transfer_date |
string |
No | YYYY-MM-DD, optionally followed by a time (HH:MM or HH:MM:SS), in the site’s timezone. Defaults to now. It becomes the date of the ledger rows. |
reference |
string |
No | Your own reference, at most 50 characters. Left empty, the document gets TR-YYYY-NNNN (year of the document date, id padded to four digits) when it is finalized. |
details |
string |
No | HTML. Becomes the reason on the ledger rows. |
assigned_user_id |
integer |
No | A WordPress user id. Not checked. |
lines |
array |
No | The lines. See below. |
Lines
| Field | Type | Required | Description |
|---|---|---|---|
item_id |
integer |
Yes | The item. Checked when the document is finalized, not before. |
quantity |
number |
Yes | Greater than zero. Always positive: the document type decides the direction. |
unit_id |
integer |
No | The unit the quantity is in PRO. |
cost_per_unit |
number |
No | Not negative. Defaults to 0. The costing method can replace it when the document is finalized. |
notes |
string |
No | Plain text. |
Lines with neither an item nor a quantity are skipped. Lines are kept in the order sent, numbered from 1 in position. A new document is always a draft: a status you send is ignored. Nothing reaches stock until you finalize it. If a line is refused, nothing is saved.
{
"from_warehouse_id": 1,
"to_warehouse_id": 2,
"lines": [
{
"item_id": 42,
"quantity": 40
}
]
}
Responses
201 Created. data is the new draft with its lines.
{
"success": true,
"data": {
"id": 12,
"reference": "",
"from_warehouse_id": 1,
"to_warehouse_id": 2,
"transfer_date": "2026-11-12 10:30:00",
"status": "draft",
"finalized_at": null,
"finalized_by": null,
"batch_id": "",
"line_count": 1,
"total_quantity": 0.0,
"total_cost": 0.0,
"details": "",
"attachments": null,
"assigned_user_id": null,
"created_by": 1,
"modified_by": 1,
"created_at": "2026-11-12 09:30:00",
"modified_at": "2026-11-12 09:30:00",
"deleted_at": null,
"lines": [
{
"id": 77,
"transfer_id": 12,
"item_id": 42,
"unit_id": null,
"quantity": 40.0,
"cost_per_unit": 4.2,
"total_cost": 0.0,
"out_transaction_id": 0,
"in_transaction_id": 0,
"notes": "",
"position": 1,
"created_by": "1",
"modified_by": "1",
"created_at": "2026-11-12 09:30:00",
"modified_at": "2026-11-12 09:30:00",
"deleted_at": null,
"item_name": "Printer paper A4",
"item_sku": "PAP-A4",
"item_type": "consumable",
"unit_code": null
}
],
"from_warehouse_name": "Main warehouse",
"to_warehouse_name": "Shop floor"
}
}
| Field | Type | Meaning |
|---|---|---|
data.id |
integer |
The transfer’s id |
data.status |
string |
draft or finalized |
data.transfer_date |
string |
Document date, in the site’s timezone |
data.from_warehouse_name |
string|null |
The source warehouse’s name |
data.to_warehouse_name |
string|null |
The destination warehouse’s name |
data.lines[].out_transaction_id |
integer |
The ledger row taking stock out. 0 until finalized |
data.lines[].in_transaction_id |
integer |
The ledger row putting it in. 0 until finalized |
Errors
| Status | Code | When |
|---|---|---|
422 |
plugixa_inventory_invalid_date |
transfer_date is not a real date in the accepted form. |
422 |
plugixa_inventory_missing_warehouse |
A warehouse is missing. data.field names it. |
422 |
plugixa_inventory_same_warehouse |
Both warehouses are the same. |
422 |
plugixa_inventory_unknown_warehouse |
A warehouse is not live. data.field names it. |
422 |
plugixa_inventory_too_long |
A text field is longer than its column. data.field names it and data.max gives the limit. |
422 |
plugixa_inventory_line_needs_item |
A line has a quantity but no item_id. |
422 |
plugixa_inventory_line_needs_quantity |
A line has an item_id but no quantity above zero. data.item_id names it. |
422 |
plugixa_inventory_not_a_number |
A line’s number is not a number. data.line (from 1) and data.item_id say which. |
422 |
plugixa_inventory_negative_number |
A line’s cost_per_unit (or weight) is negative. |
500 |
plugixa_inventory_insert_failed |
The database refused to save the transfer. |
Plus the errors every endpoint can return.
Examples
curl -s -X POST "https://your-site.example/wp-json/plugixa-inventory/v1/transfers" \
-u "admin:$WP_APP_PASSWORD" \
-H "Content-Type: application/json" \
-d '{"from_warehouse_id":1,"to_warehouse_id":2,"lines":[{"item_id":42,"quantity":40}]}'
<?php
$ch = curl_init('https://your-site.example/wp-json/plugixa-inventory/v1/transfers');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERPWD => 'admin:' . getenv('WP_APP_PASSWORD'),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'from_warehouse_id' => 1,
'to_warehouse_id' => 2,
'lines' => [
[
'item_id' => 42,
'quantity' => 40,
],
],
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
const auth = 'Basic ' + btoa(`admin:${process.env.WP_APP_PASSWORD}`);
const response = await fetch('https://your-site.example/wp-json/plugixa-inventory/v1/transfers', {
method: 'POST',
headers: { 'Authorization': auth, 'Content-Type': 'application/json' },
body: JSON.stringify({
from_warehouse_id: 1,
to_warehouse_id: 2,
lines: [
{
item_id: 42,
quantity: 40,
},
],
}),
});
const { data } = await response.json();
Quick Links
Share