Inventory & Stock
Products
CRUD for the product catalogue. Each product tracks its aggregate stock_quantity across all warehouse locations.
List Products
const { data } = await $fetch('https://dev.taxmtd.uk/api/products', {
params: { entityId: '...' }
})
// Response shape
interface Product {
id: string
name: string
sku: string
description: string | null
category: string
unit_price: number
cost_price: number
stock_qty: number
low_stock_threshold: number
unit: string
notes: string | null
barcode: string | null
active: boolean
entity_id: string | null
stock_levels: WarehouseStock[]
}
res = requests.get(
"https://dev.taxmtd.uk/api/products",
params={"entityId": "..."},
cookies=session_cookies,
)
products = res.json()["data"]
$response = Http::withCookies($session)
->get('https://dev.taxmtd.uk/api/products', ['entityId' => '...']);
$products = $response->json()['data'];
let res = client
.get("https://dev.taxmtd.uk/api/products")
.query(&[("entityId", "...")])
.send().await?
.json::<serde_json::Value>().await?;
let products = &res["data"];
curl "https://dev.taxmtd.uk/api/products?entityId=UUID"
Create Product
const { data } = await $fetch('https://dev.taxmtd.uk/api/products', {
method: 'POST',
body: {
name: 'Widget Pro',
sku: 'WDG-001', // optional - auto-generated if omitted
description: 'Premium widget',
category: 'Electronics',
unit_price: 29.99,
cost_price: 12.50,
stock_qty: 100,
low_stock_threshold: 10, // default: 5
unit: 'pcs', // default: 'pcs'
notes: 'Fragile',
barcode: '5012345678901',
entity_id: '...'
}
})
res = requests.post(
"https://dev.taxmtd.uk/api/products",
json={
"name": "Widget Pro",
"sku": "WDG-001",
"description": "Premium widget",
"category": "Electronics",
"unit_price": 29.99,
"cost_price": 12.50,
"stock_qty": 100,
"low_stock_threshold": 10,
"unit": "pcs",
"notes": "Fragile",
"barcode": "5012345678901",
"entity_id": "...",
},
cookies=session_cookies,
)
product = res.json()["data"]
$response = Http::withCookies($session)->post(
'https://dev.taxmtd.uk/api/products',
[
'name' => 'Widget Pro',
'sku' => 'WDG-001',
'description' => 'Premium widget',
'category' => 'Electronics',
'unit_price' => 29.99,
'cost_price' => 12.50,
'stock_qty' => 100,
'low_stock_threshold' => 10,
'unit' => 'pcs',
'notes' => 'Fragile',
'barcode' => '5012345678901',
'entity_id' => '...',
]
);
$product = $response->json()['data'];
let res = client.post("https://dev.taxmtd.uk/api/products")
.json(&serde_json::json!({
"name": "Widget Pro",
"sku": "WDG-001",
"description": "Premium widget",
"category": "Electronics",
"unit_price": 29.99,
"cost_price": 12.50,
"stock_qty": 100,
"low_stock_threshold": 10,
"unit": "pcs",
"notes": "Fragile",
"barcode": "5012345678901",
"entity_id": "..."
}))
.send().await?
.json::<serde_json::Value>().await?;
let product = &res["data"];
curl -X POST https://dev.taxmtd.uk/api/products \
-H "Content-Type: application/json" \
-d '{"name":"Widget Pro","sku":"WDG-001","unit_price":29.99,"cost_price":12.50,"stock_qty":100,"entity_id":"UUID"}'
Update Product
Send id plus any updatable fields: name, sku, description, category, unit_price, cost_price, stock_qty, low_stock_threshold, unit, active, notes, barcode.
await $fetch('https://dev.taxmtd.uk/api/products', {
method: 'PUT',
body: { id: '...', unit_price: 34.99, active: false }
})
res = requests.put(
"https://dev.taxmtd.uk/api/products",
json={"id": "UUID", "unit_price": 34.99, "active": False},
cookies=session_cookies,
)
$response = Http::withCookies($session)->put(
'https://dev.taxmtd.uk/api/products',
['id' => 'UUID', 'unit_price' => 34.99, 'active' => false]
);
let res = client.put("https://dev.taxmtd.uk/api/products")
.json(&serde_json::json!({
"id": "UUID",
"unit_price": 34.99,
"active": false
}))
.send().await?;
curl -X PUT https://dev.taxmtd.uk/api/products \
-H "Content-Type: application/json" \
-d '{"id":"UUID","unit_price":34.99,"active":false}'
Delete Product
await $fetch('https://dev.taxmtd.uk/api/products', {
method: 'DELETE',
body: { id: '...' }
})
// Returns: { data: { success: true } }
res = requests.delete(
"https://dev.taxmtd.uk/api/products",
json={"id": "UUID"},
cookies=session_cookies,
)
$response = Http::withCookies($session)->delete(
'https://dev.taxmtd.uk/api/products',
['id' => 'UUID']
);
let res = client.delete("https://dev.taxmtd.uk/api/products")
.json(&serde_json::json!({"id": "UUID"}))
.send().await?;
curl -X DELETE https://dev.taxmtd.uk/api/products \
-H "Content-Type: application/json" \
-d '{"id":"UUID"}'
Warehouse Locations
Manage physical storage locations. Each location has a status (active or inactive).
List Locations
const { data } = await $fetch('https://dev.taxmtd.uk/api/warehouse-locations', {
params: { entityId: '...' }
})
// Response shape
interface WarehouseLocation {
id: string
name: string
address: string | null
status: 'active' | 'inactive'
entity_id: string
}
res = requests.get(
"https://dev.taxmtd.uk/api/warehouse-locations",
params={"entityId": "..."},
cookies=session_cookies,
)
locations = res.json()["data"]
$response = Http::withCookies($session)
->get('https://dev.taxmtd.uk/api/warehouse-locations', ['entityId' => '...']);
$locations = $response->json()['data'];
let res = client
.get("https://dev.taxmtd.uk/api/warehouse-locations")
.query(&[("entityId", "...")])
.send().await?
.json::<serde_json::Value>().await?;
let locations = &res["data"];
curl "https://dev.taxmtd.uk/api/warehouse-locations?entityId=UUID"
Create Location
const { data } = await $fetch('https://dev.taxmtd.uk/api/warehouse-locations', {
method: 'POST',
body: {
name: 'Main Warehouse',
address: '10 Industrial Park, London',
entity_id: '...' // required
}
})
res = requests.post(
"https://dev.taxmtd.uk/api/warehouse-locations",
json={
"name": "Main Warehouse",
"address": "10 Industrial Park, London",
"entity_id": "...",
},
cookies=session_cookies,
)
location = res.json()["data"]
$response = Http::withCookies($session)->post(
'https://dev.taxmtd.uk/api/warehouse-locations',
[
'name' => 'Main Warehouse',
'address' => '10 Industrial Park, London',
'entity_id' => '...',
]
);
$location = $response->json()['data'];
let res = client.post("https://dev.taxmtd.uk/api/warehouse-locations")
.json(&serde_json::json!({
"name": "Main Warehouse",
"address": "10 Industrial Park, London",
"entity_id": "..."
}))
.send().await?
.json::<serde_json::Value>().await?;
let location = &res["data"];
curl -X POST https://dev.taxmtd.uk/api/warehouse-locations \
-H "Content-Type: application/json" \
-d '{"name":"Main Warehouse","address":"10 Industrial Park, London","entity_id":"UUID"}'
Update Location
Allowed fields: name, address, status.
await $fetch('https://dev.taxmtd.uk/api/warehouse-locations', {
method: 'PUT',
body: { id: '...', status: 'inactive' }
})
res = requests.put(
"https://dev.taxmtd.uk/api/warehouse-locations",
json={"id": "UUID", "status": "inactive"},
cookies=session_cookies,
)
$response = Http::withCookies($session)->put(
'https://dev.taxmtd.uk/api/warehouse-locations',
['id' => 'UUID', 'status' => 'inactive']
);
let res = client.put("https://dev.taxmtd.uk/api/warehouse-locations")
.json(&serde_json::json!({
"id": "UUID",
"status": "inactive"
}))
.send().await?;
curl -X PUT https://dev.taxmtd.uk/api/warehouse-locations \
-H "Content-Type: application/json" \
-d '{"id":"UUID","status":"inactive"}'
Delete Location
await $fetch('https://dev.taxmtd.uk/api/warehouse-locations', {
method: 'DELETE',
body: { id: '...' }
})
// Returns: { data: { success: true } }
res = requests.delete(
"https://dev.taxmtd.uk/api/warehouse-locations",
json={"id": "UUID"},
cookies=session_cookies,
)
$response = Http::withCookies($session)->delete(
'https://dev.taxmtd.uk/api/warehouse-locations',
['id' => 'UUID']
);
let res = client.delete("https://dev.taxmtd.uk/api/warehouse-locations")
.json(&serde_json::json!({"id": "UUID"}))
.send().await?;
curl -X DELETE https://dev.taxmtd.uk/api/warehouse-locations \
-H "Content-Type: application/json" \
-d '{"id":"UUID"}'
Warehouse Stock
Per-product, per-location stock levels. POST performs an upsert - if a row already exists for the given product_id + location_id pair, it updates instead of creating a duplicate.
List Stock Levels
const { data } = await $fetch('https://dev.taxmtd.uk/api/warehouse-stock', {
params: { entityId: '...' }
})
// Response shape - product_id and location_id are expanded
interface WarehouseStock {
id: string
product_id: { name: string; sku: string }
location_id: { name: string }
quantity: number
reorder_point: number
reserved_quantity: number
entity_id: string | null
}
res = requests.get(
"https://dev.taxmtd.uk/api/warehouse-stock",
params={"entityId": "..."},
cookies=session_cookies,
)
stock_levels = res.json()["data"]
$response = Http::withCookies($session)
->get('https://dev.taxmtd.uk/api/warehouse-stock', ['entityId' => '...']);
$stockLevels = $response->json()['data'];
let res = client
.get("https://dev.taxmtd.uk/api/warehouse-stock")
.query(&[("entityId", "...")])
.send().await?
.json::<serde_json::Value>().await?;
let stock_levels = &res["data"];
curl "https://dev.taxmtd.uk/api/warehouse-stock?entityId=UUID&locationId=UUID"
Upsert Stock Level
const { data } = await $fetch('https://dev.taxmtd.uk/api/warehouse-stock', {
method: 'POST',
body: {
product_id: '...',
location_id: '...',
quantity: 50,
reorder_point: 10,
entity_id: '...'
}
})
res = requests.post(
"https://dev.taxmtd.uk/api/warehouse-stock",
json={
"product_id": "...",
"location_id": "...",
"quantity": 50,
"reorder_point": 10,
"entity_id": "...",
},
cookies=session_cookies,
)
stock = res.json()["data"]
$response = Http::withCookies($session)->post(
'https://dev.taxmtd.uk/api/warehouse-stock',
[
'product_id' => '...',
'location_id' => '...',
'quantity' => 50,
'reorder_point' => 10,
'entity_id' => '...',
]
);
$stock = $response->json()['data'];
let res = client.post("https://dev.taxmtd.uk/api/warehouse-stock")
.json(&serde_json::json!({
"product_id": "...",
"location_id": "...",
"quantity": 50,
"reorder_point": 10,
"entity_id": "..."
}))
.send().await?
.json::<serde_json::Value>().await?;
let stock = &res["data"];
curl -X POST https://dev.taxmtd.uk/api/warehouse-stock \
-H "Content-Type: application/json" \
-d '{"product_id":"UUID","location_id":"UUID","quantity":50,"reorder_point":10,"entity_id":"UUID"}'
Update Stock Level
Allowed fields: quantity, reorder_point, reserved_quantity.
await $fetch('https://dev.taxmtd.uk/api/warehouse-stock', {
method: 'PUT',
body: { id: '...', quantity: 75, reorder_point: 15 }
})
res = requests.put(
"https://dev.taxmtd.uk/api/warehouse-stock",
json={"id": "UUID", "quantity": 75, "reorder_point": 15},
cookies=session_cookies,
)
$response = Http::withCookies($session)->put(
'https://dev.taxmtd.uk/api/warehouse-stock',
['id' => 'UUID', 'quantity' => 75, 'reorder_point' => 15]
);
let res = client.put("https://dev.taxmtd.uk/api/warehouse-stock")
.json(&serde_json::json!({
"id": "UUID",
"quantity": 75,
"reorder_point": 15
}))
.send().await?;
curl -X PUT https://dev.taxmtd.uk/api/warehouse-stock \
-H "Content-Type: application/json" \
-d '{"id":"UUID","quantity":75,"reorder_point":15}'
Stock Movements
Immutable ledger of quantity changes. Recording a movement automatically updates both the relevant warehouse_stock row and the product's aggregate stock_quantity.
List Movements
purchase, sale, adjustment, return, transfer_in, transfer_out, stock_takeconst { data } = await $fetch('https://dev.taxmtd.uk/api/stock-movements', {
params: { entityId: '...', type: 'purchase', dateFrom: '2026-01-01' }
})
// Response shape
interface StockMovement {
id: string
product_id: { name: string } | string
qty: number
type: string
location_id: string | null
reference: string | null
notes: string | null
date: string
entity_id: string | null
}
res = requests.get(
"https://dev.taxmtd.uk/api/stock-movements",
params={
"entityId": "...",
"type": "purchase",
"dateFrom": "2026-01-01",
},
cookies=session_cookies,
)
movements = res.json()["data"]
$response = Http::withCookies($session)
->get('https://dev.taxmtd.uk/api/stock-movements', [
'entityId' => '...',
'type' => 'purchase',
'dateFrom' => '2026-01-01',
]);
$movements = $response->json()['data'];
let res = client
.get("https://dev.taxmtd.uk/api/stock-movements")
.query(&[
("entityId", "..."),
("type", "purchase"),
("dateFrom", "2026-01-01"),
])
.send().await?
.json::<serde_json::Value>().await?;
let movements = &res["data"];
curl "https://dev.taxmtd.uk/api/stock-movements?entityId=UUID&type=sale&dateFrom=2026-01-01&dateTo=2026-03-31"
Record Movement
Use positive values for stock increases (purchase, return, transfer_in, adjustment up) and negative values for decreases (sale, transfer_out, adjustment down). When location_id is provided, the warehouse stock row is updated (or created); otherwise the product's aggregate stock is adjusted directly.
const { data } = await $fetch('https://dev.taxmtd.uk/api/stock-movements', {
method: 'POST',
body: {
product_id: '...',
qty: 25,
type: 'purchase',
location_id: '...', // optional - ties movement to a warehouse
reference: 'PO-001', // optional
notes: 'Supplier restock',
date: '2026-04-01', // defaults to today
entity_id: '...'
}
})
res = requests.post(
"https://dev.taxmtd.uk/api/stock-movements",
json={
"product_id": "...",
"qty": 25,
"type": "purchase",
"location_id": "...",
"reference": "PO-001",
"notes": "Supplier restock",
"date": "2026-04-01",
"entity_id": "...",
},
cookies=session_cookies,
)
movement = res.json()["data"]
$response = Http::withCookies($session)->post(
'https://dev.taxmtd.uk/api/stock-movements',
[
'product_id' => '...',
'qty' => 25,
'type' => 'purchase',
'location_id' => '...',
'reference' => 'PO-001',
'notes' => 'Supplier restock',
'date' => '2026-04-01',
'entity_id' => '...',
]
);
$movement = $response->json()['data'];
let res = client.post("https://dev.taxmtd.uk/api/stock-movements")
.json(&serde_json::json!({
"product_id": "...",
"qty": 25,
"type": "purchase",
"location_id": "...",
"reference": "PO-001",
"notes": "Supplier restock",
"date": "2026-04-01",
"entity_id": "..."
}))
.send().await?
.json::<serde_json::Value>().await?;
let movement = &res["data"];
curl -X POST https://dev.taxmtd.uk/api/stock-movements \
-H "Content-Type: application/json" \
-d '{"product_id":"UUID","qty":25,"type":"purchase","location_id":"UUID","reference":"PO-001","entity_id":"UUID"}'
Transfer Orders
Move stock between warehouse locations with full status-lifecycle enforcement.
Status transitions:
draft→in_transit- validates source stock availability, createstransfer_outmovements, decrements source warehousein_transit→completed- createstransfer_inmovements, increments destination warehouse, recalculates product totalsdraftorin_transit→cancelled- if in-transit, reverses source deductions withtransfer_cancelledmovementscompletedorders cannot be cancelled
List Transfer Orders
const { data } = await $fetch('https://dev.taxmtd.uk/api/transfer-orders', {
params: { entityId: '...' }
})
// Response shape
interface TransferOrder {
id: string
number: string // e.g. "TO-001"
source_location_id: { name: string }
destination_location_id: { name: string }
status: 'draft' | 'in_transit' | 'completed' | 'cancelled'
notes: string | null
entity_id: string
date_created: string
date_completed: string | null
lines: TransferOrderLine[]
}
interface TransferOrderLine {
id: string
product_id: { name: string; sku: string }
quantity: number
received_quantity: number
}
res = requests.get(
"https://dev.taxmtd.uk/api/transfer-orders",
params={"entityId": "..."},
cookies=session_cookies,
)
orders = res.json()["data"]
$response = Http::withCookies($session)
->get('https://dev.taxmtd.uk/api/transfer-orders', ['entityId' => '...']);
$orders = $response->json()['data'];
let res = client
.get("https://dev.taxmtd.uk/api/transfer-orders")
.query(&[("entityId", "...")])
.send().await?
.json::<serde_json::Value>().await?;
let orders = &res["data"];
curl "https://dev.taxmtd.uk/api/transfer-orders?entityId=UUID"
Create Transfer Order
Source and destination must be different locations. At least one line is required.
const { data } = await $fetch('https://dev.taxmtd.uk/api/transfer-orders', {
method: 'POST',
body: {
source_location_id: '...',
destination_location_id: '...',
entity_id: '...',
notes: 'Rebalance stock for Q2',
lines: [
{ product_id: '...', quantity: 20 },
{ product_id: '...', quantity: 10 }
]
}
})
// Returns the created order with auto-generated number (e.g. "TO-001")
res = requests.post(
"https://dev.taxmtd.uk/api/transfer-orders",
json={
"source_location_id": "...",
"destination_location_id": "...",
"entity_id": "...",
"notes": "Rebalance stock for Q2",
"lines": [
{"product_id": "...", "quantity": 20},
{"product_id": "...", "quantity": 10},
],
},
cookies=session_cookies,
)
order = res.json()["data"]
$response = Http::withCookies($session)->post(
'https://dev.taxmtd.uk/api/transfer-orders',
[
'source_location_id' => '...',
'destination_location_id' => '...',
'entity_id' => '...',
'notes' => 'Rebalance stock for Q2',
'lines' => [
['product_id' => '...', 'quantity' => 20],
['product_id' => '...', 'quantity' => 10],
],
]
);
$order = $response->json()['data'];
let res = client.post("https://dev.taxmtd.uk/api/transfer-orders")
.json(&serde_json::json!({
"source_location_id": "...",
"destination_location_id": "...",
"entity_id": "...",
"notes": "Rebalance stock for Q2",
"lines": [
{"product_id": "...", "quantity": 20},
{"product_id": "...", "quantity": 10}
]
}))
.send().await?
.json::<serde_json::Value>().await?;
let order = &res["data"];
curl -X POST https://dev.taxmtd.uk/api/transfer-orders \
-H "Content-Type: application/json" \
-d '{"source_location_id":"UUID","destination_location_id":"UUID","entity_id":"UUID","lines":[{"product_id":"UUID","quantity":20}]}'
Update Transfer Order Status
// Ship the order
await $fetch('https://dev.taxmtd.uk/api/transfer-orders', {
method: 'PUT',
body: { id: '...', status: 'in_transit' }
})
// Receive the order
await $fetch('https://dev.taxmtd.uk/api/transfer-orders', {
method: 'PUT',
body: { id: '...', status: 'completed' }
})
// Cancel (draft or in-transit only)
await $fetch('https://dev.taxmtd.uk/api/transfer-orders', {
method: 'PUT',
body: { id: '...', status: 'cancelled' }
})
# Ship the order
requests.put(
"https://dev.taxmtd.uk/api/transfer-orders",
json={"id": "UUID", "status": "in_transit"},
cookies=session_cookies,
)
# Receive the order
requests.put(
"https://dev.taxmtd.uk/api/transfer-orders",
json={"id": "UUID", "status": "completed"},
cookies=session_cookies,
)
# Cancel (draft or in-transit only)
requests.put(
"https://dev.taxmtd.uk/api/transfer-orders",
json={"id": "UUID", "status": "cancelled"},
cookies=session_cookies,
)
// Ship the order
Http::withCookies($session)->put(
'https://dev.taxmtd.uk/api/transfer-orders',
['id' => 'UUID', 'status' => 'in_transit']
);
// Receive the order
Http::withCookies($session)->put(
'https://dev.taxmtd.uk/api/transfer-orders',
['id' => 'UUID', 'status' => 'completed']
);
// Cancel (draft or in-transit only)
Http::withCookies($session)->put(
'https://dev.taxmtd.uk/api/transfer-orders',
['id' => 'UUID', 'status' => 'cancelled']
);
// Ship the order
client.put("https://dev.taxmtd.uk/api/transfer-orders")
.json(&serde_json::json!({"id": "UUID", "status": "in_transit"}))
.send().await?;
// Receive the order
client.put("https://dev.taxmtd.uk/api/transfer-orders")
.json(&serde_json::json!({"id": "UUID", "status": "completed"}))
.send().await?;
// Cancel (draft or in-transit only)
client.put("https://dev.taxmtd.uk/api/transfer-orders")
.json(&serde_json::json!({"id": "UUID", "status": "cancelled"}))
.send().await?;
# Ship
curl -X PUT https://dev.taxmtd.uk/api/transfer-orders \
-H "Content-Type: application/json" \
-d '{"id":"UUID","status":"in_transit"}'
# Complete
curl -X PUT https://dev.taxmtd.uk/api/transfer-orders \
-H "Content-Type: application/json" \
-d '{"id":"UUID","status":"completed"}'
Delete Transfer Order
Only draft orders can be deleted. Lines are removed automatically.
await $fetch('https://dev.taxmtd.uk/api/transfer-orders', {
method: 'DELETE',
body: { id: '...' }
})
// Returns: { data: { success: true } }
res = requests.delete(
"https://dev.taxmtd.uk/api/transfer-orders",
json={"id": "UUID"},
cookies=session_cookies,
)
$response = Http::withCookies($session)->delete(
'https://dev.taxmtd.uk/api/transfer-orders',
['id' => 'UUID']
);
let res = client.delete("https://dev.taxmtd.uk/api/transfer-orders")
.json(&serde_json::json!({"id": "UUID"}))
.send().await?;
curl -X DELETE https://dev.taxmtd.uk/api/transfer-orders \
-H "Content-Type: application/json" \
-d '{"id":"UUID"}'
Stock Takes
Physical inventory counts with automatic variance adjustments. Creating a stock take snapshots the current warehouse stock as system_quantity on each line.
Status transitions:
draft→in_progress- counting beginsin_progress→completed- calculates variance per line, createsstock_takemovements, updates warehouse stock to match counted quantities, recalculates product totalsdraftorin_progress→cancelledcompletedstock takes cannot be cancelled
List Stock Takes
const { data } = await $fetch('https://dev.taxmtd.uk/api/stock-takes', {
params: { entityId: '...' }
})
// Response shape
interface StockTake {
id: string
number: string // e.g. "ST-001"
location_id: { name: string }
status: 'draft' | 'in_progress' | 'completed' | 'cancelled'
notes: string | null
entity_id: string
date_created: string
date_completed: string | null
lines: StockTakeLine[]
}
interface StockTakeLine {
id: string
product_id: { name: string; sku: string }
system_quantity: number
counted_quantity: number | null
}
res = requests.get(
"https://dev.taxmtd.uk/api/stock-takes",
params={"entityId": "..."},
cookies=session_cookies,
)
stock_takes = res.json()["data"]
$response = Http::withCookies($session)
->get('https://dev.taxmtd.uk/api/stock-takes', ['entityId' => '...']);
$stockTakes = $response->json()['data'];
let res = client
.get("https://dev.taxmtd.uk/api/stock-takes")
.query(&[("entityId", "...")])
.send().await?
.json::<serde_json::Value>().await?;
let stock_takes = &res["data"];
curl "https://dev.taxmtd.uk/api/stock-takes?entityId=UUID"
Create Stock Take
Lines are auto-populated from the current warehouse_stock for the given location.
const { data } = await $fetch('https://dev.taxmtd.uk/api/stock-takes', {
method: 'POST',
body: {
location_id: '...', // required
entity_id: '...', // required
notes: 'Monthly count - April 2026'
}
})
// Returns the stock take with pre-populated lines (system_quantity set, counted_quantity null)
res = requests.post(
"https://dev.taxmtd.uk/api/stock-takes",
json={
"location_id": "...",
"entity_id": "...",
"notes": "Monthly count - April 2026",
},
cookies=session_cookies,
)
stock_take = res.json()["data"]
$response = Http::withCookies($session)->post(
'https://dev.taxmtd.uk/api/stock-takes',
[
'location_id' => '...',
'entity_id' => '...',
'notes' => 'Monthly count - April 2026',
]
);
$stockTake = $response->json()['data'];
let res = client.post("https://dev.taxmtd.uk/api/stock-takes")
.json(&serde_json::json!({
"location_id": "...",
"entity_id": "...",
"notes": "Monthly count - April 2026"
}))
.send().await?
.json::<serde_json::Value>().await?;
let stock_take = &res["data"];
curl -X POST https://dev.taxmtd.uk/api/stock-takes \
-H "Content-Type: application/json" \
-d '{"location_id":"UUID","entity_id":"UUID","notes":"Monthly count"}'
Update Stock Take
You can update counted quantities and/or transition the status in a single request. Omit status to perform a lines-only update.
// Record counts
await $fetch('https://dev.taxmtd.uk/api/stock-takes', {
method: 'PUT',
body: {
id: '...',
lines: [
{ id: 'line-uuid-1', counted_quantity: 48 },
{ id: 'line-uuid-2', counted_quantity: 100 }
]
}
})
// Start counting
await $fetch('https://dev.taxmtd.uk/api/stock-takes', {
method: 'PUT',
body: { id: '...', status: 'in_progress' }
})
// Complete - applies variance adjustments
await $fetch('https://dev.taxmtd.uk/api/stock-takes', {
method: 'PUT',
body: { id: '...', status: 'completed' }
})
# Record counts
requests.put(
"https://dev.taxmtd.uk/api/stock-takes",
json={
"id": "UUID",
"lines": [
{"id": "line-uuid-1", "counted_quantity": 48},
{"id": "line-uuid-2", "counted_quantity": 100},
],
},
cookies=session_cookies,
)
# Start counting
requests.put(
"https://dev.taxmtd.uk/api/stock-takes",
json={"id": "UUID", "status": "in_progress"},
cookies=session_cookies,
)
# Complete - applies variance adjustments
requests.put(
"https://dev.taxmtd.uk/api/stock-takes",
json={"id": "UUID", "status": "completed"},
cookies=session_cookies,
)
// Record counts
Http::withCookies($session)->put(
'https://dev.taxmtd.uk/api/stock-takes',
[
'id' => 'UUID',
'lines' => [
['id' => 'line-uuid-1', 'counted_quantity' => 48],
['id' => 'line-uuid-2', 'counted_quantity' => 100],
],
]
);
// Start counting
Http::withCookies($session)->put(
'https://dev.taxmtd.uk/api/stock-takes',
['id' => 'UUID', 'status' => 'in_progress']
);
// Complete - applies variance adjustments
Http::withCookies($session)->put(
'https://dev.taxmtd.uk/api/stock-takes',
['id' => 'UUID', 'status' => 'completed']
);
// Record counts
client.put("https://dev.taxmtd.uk/api/stock-takes")
.json(&serde_json::json!({
"id": "UUID",
"lines": [
{"id": "line-uuid-1", "counted_quantity": 48},
{"id": "line-uuid-2", "counted_quantity": 100}
]
}))
.send().await?;
// Start counting
client.put("https://dev.taxmtd.uk/api/stock-takes")
.json(&serde_json::json!({"id": "UUID", "status": "in_progress"}))
.send().await?;
// Complete - applies variance adjustments
client.put("https://dev.taxmtd.uk/api/stock-takes")
.json(&serde_json::json!({"id": "UUID", "status": "completed"}))
.send().await?;
# Record counts
curl -X PUT https://dev.taxmtd.uk/api/stock-takes \
-H "Content-Type: application/json" \
-d '{"id":"UUID","lines":[{"id":"LINE-UUID","counted_quantity":48}]}'
# Complete
curl -X PUT https://dev.taxmtd.uk/api/stock-takes \
-H "Content-Type: application/json" \
-d '{"id":"UUID","status":"completed"}'
Delete Stock Take
Only draft or cancelled stock takes can be deleted. Lines are removed automatically.
await $fetch('https://dev.taxmtd.uk/api/stock-takes', {
method: 'DELETE',
body: { id: '...' }
})
// Returns: { data: { success: true } }
res = requests.delete(
"https://dev.taxmtd.uk/api/stock-takes",
json={"id": "UUID"},
cookies=session_cookies,
)
$response = Http::withCookies($session)->delete(
'https://dev.taxmtd.uk/api/stock-takes',
['id' => 'UUID']
);
let res = client.delete("https://dev.taxmtd.uk/api/stock-takes")
.json(&serde_json::json!({"id": "UUID"}))
.send().await?;
curl -X DELETE https://dev.taxmtd.uk/api/stock-takes \
-H "Content-Type: application/json" \
-d '{"id":"UUID"}'
Stock Valuation Report
Returns a per-product, per-warehouse cost and retail valuation with margin calculations.
const { data, summary } = await $fetch('https://dev.taxmtd.uk/api/reports/stock-valuation', {
params: { entityId: '...' }
})
// data[] shape
interface StockValuationRow {
product_id: string
product_name: string
product_sku: string
category: string
location_id: string
location_name: string
quantity: number
cost_price: number
unit_price: number
total_cost: number // quantity * cost_price
total_retail: number // quantity * unit_price
margin: number // percentage
}
// summary shape
interface StockValuationSummary {
totalCostValue: number
totalRetailValue: number
totalUnits: number
avgMargin: number // percentage
}
res = requests.get(
"https://dev.taxmtd.uk/api/reports/stock-valuation",
params={"entityId": "..."},
cookies=session_cookies,
)
body = res.json()
rows = body["data"]
summary = body["summary"]
$response = Http::withCookies($session)
->get('https://dev.taxmtd.uk/api/reports/stock-valuation', ['entityId' => '...']);
$rows = $response->json()['data'];
$summary = $response->json()['summary'];
let res = client
.get("https://dev.taxmtd.uk/api/reports/stock-valuation")
.query(&[("entityId", "...")])
.send().await?
.json::<serde_json::Value>().await?;
let rows = &res["data"];
let summary = &res["summary"];
curl "https://dev.taxmtd.uk/api/reports/stock-valuation?entityId=UUID"
# Filter to a single warehouse
curl "https://dev.taxmtd.uk/api/reports/stock-valuation?entityId=UUID&locationId=UUID"