Inventory & Stock

Product catalogue, warehouse locations, stock levels, movements, transfer orders, stock takes, and valuation reports.

Products

CRUD for the product catalogue. Each product tracks its aggregate stock_quantity across all warehouse locations.

List Products

entityId
string
Filter by entity UUID
JavaScript
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[]
}
Python
res = requests.get(
    "https://dev.taxmtd.uk/api/products",
    params={"entityId": "..."},
    cookies=session_cookies,
)
products = res.json()["data"]
PHP
$response = Http::withCookies($session)
    ->get('https://dev.taxmtd.uk/api/products', ['entityId' => '...']);

$products = $response->json()['data'];
Rust
let res = client
    .get("https://dev.taxmtd.uk/api/products")
    .query(&[("entityId", "...")])
    .send().await?
    .json::<serde_json::Value>().await?;

let products = &res["data"];
cURL
curl "https://dev.taxmtd.uk/api/products?entityId=UUID"

Create Product

JavaScript
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: '...'
  }
})
Python
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"]
PHP
$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'];
Rust
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
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.

JavaScript
await $fetch('https://dev.taxmtd.uk/api/products', {
  method: 'PUT',
  body: { id: '...', unit_price: 34.99, active: false }
})
Python
res = requests.put(
    "https://dev.taxmtd.uk/api/products",
    json={"id": "UUID", "unit_price": 34.99, "active": False},
    cookies=session_cookies,
)
PHP
$response = Http::withCookies($session)->put(
    'https://dev.taxmtd.uk/api/products',
    ['id' => 'UUID', 'unit_price' => 34.99, 'active' => false]
);
Rust
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
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

JavaScript
await $fetch('https://dev.taxmtd.uk/api/products', {
  method: 'DELETE',
  body: { id: '...' }
})
// Returns: { data: { success: true } }
Python
res = requests.delete(
    "https://dev.taxmtd.uk/api/products",
    json={"id": "UUID"},
    cookies=session_cookies,
)
PHP
$response = Http::withCookies($session)->delete(
    'https://dev.taxmtd.uk/api/products',
    ['id' => 'UUID']
);
Rust
let res = client.delete("https://dev.taxmtd.uk/api/products")
    .json(&serde_json::json!({"id": "UUID"}))
    .send().await?;
cURL
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

entityId
string
Filter by entity UUID
JavaScript
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
}
Python
res = requests.get(
    "https://dev.taxmtd.uk/api/warehouse-locations",
    params={"entityId": "..."},
    cookies=session_cookies,
)
locations = res.json()["data"]
PHP
$response = Http::withCookies($session)
    ->get('https://dev.taxmtd.uk/api/warehouse-locations', ['entityId' => '...']);

$locations = $response->json()['data'];
Rust
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
curl "https://dev.taxmtd.uk/api/warehouse-locations?entityId=UUID"

Create Location

JavaScript
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
  }
})
Python
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"]
PHP
$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'];
Rust
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
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.

JavaScript
await $fetch('https://dev.taxmtd.uk/api/warehouse-locations', {
  method: 'PUT',
  body: { id: '...', status: 'inactive' }
})
Python
res = requests.put(
    "https://dev.taxmtd.uk/api/warehouse-locations",
    json={"id": "UUID", "status": "inactive"},
    cookies=session_cookies,
)
PHP
$response = Http::withCookies($session)->put(
    'https://dev.taxmtd.uk/api/warehouse-locations',
    ['id' => 'UUID', 'status' => 'inactive']
);
Rust
let res = client.put("https://dev.taxmtd.uk/api/warehouse-locations")
    .json(&serde_json::json!({
        "id": "UUID",
        "status": "inactive"
    }))
    .send().await?;
cURL
curl -X PUT https://dev.taxmtd.uk/api/warehouse-locations \
  -H "Content-Type: application/json" \
  -d '{"id":"UUID","status":"inactive"}'

Delete Location

JavaScript
await $fetch('https://dev.taxmtd.uk/api/warehouse-locations', {
  method: 'DELETE',
  body: { id: '...' }
})
// Returns: { data: { success: true } }
Python
res = requests.delete(
    "https://dev.taxmtd.uk/api/warehouse-locations",
    json={"id": "UUID"},
    cookies=session_cookies,
)
PHP
$response = Http::withCookies($session)->delete(
    'https://dev.taxmtd.uk/api/warehouse-locations',
    ['id' => 'UUID']
);
Rust
let res = client.delete("https://dev.taxmtd.uk/api/warehouse-locations")
    .json(&serde_json::json!({"id": "UUID"}))
    .send().await?;
cURL
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

productId
string
Filter by product UUID
locationId
string
Filter by warehouse location UUID
entityId
string
Filter by entity UUID
JavaScript
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
}
Python
res = requests.get(
    "https://dev.taxmtd.uk/api/warehouse-stock",
    params={"entityId": "..."},
    cookies=session_cookies,
)
stock_levels = res.json()["data"]
PHP
$response = Http::withCookies($session)
    ->get('https://dev.taxmtd.uk/api/warehouse-stock', ['entityId' => '...']);

$stockLevels = $response->json()['data'];
Rust
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
curl "https://dev.taxmtd.uk/api/warehouse-stock?entityId=UUID&locationId=UUID"

Upsert Stock Level

JavaScript
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: '...'
  }
})
Python
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"]
PHP
$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'];
Rust
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
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.

JavaScript
await $fetch('https://dev.taxmtd.uk/api/warehouse-stock', {
  method: 'PUT',
  body: { id: '...', quantity: 75, reorder_point: 15 }
})
Python
res = requests.put(
    "https://dev.taxmtd.uk/api/warehouse-stock",
    json={"id": "UUID", "quantity": 75, "reorder_point": 15},
    cookies=session_cookies,
)
PHP
$response = Http::withCookies($session)->put(
    'https://dev.taxmtd.uk/api/warehouse-stock',
    ['id' => 'UUID', 'quantity' => 75, 'reorder_point' => 15]
);
Rust
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
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

productId
string
Filter by product UUID
entityId
string
Filter by entity UUID
locationId
string
Filter by warehouse location UUID
type
string
Filter by movement type: purchase, sale, adjustment, return, transfer_in, transfer_out, stock_take
dateFrom
string
Inclusive start date (YYYY-MM-DD)
dateTo
string
Inclusive end date (YYYY-MM-DD)
JavaScript
const { 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
}
Python
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"]
PHP
$response = Http::withCookies($session)
    ->get('https://dev.taxmtd.uk/api/stock-movements', [
        'entityId' => '...',
        'type' => 'purchase',
        'dateFrom' => '2026-01-01',
    ]);

$movements = $response->json()['data'];
Rust
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
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.

JavaScript
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: '...'
  }
})
Python
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"]
PHP
$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'];
Rust
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
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, creates transfer_out movements, decrements source warehouse
  • in_transit → completed - creates transfer_in movements, increments destination warehouse, recalculates product totals
  • draft or in_transit → cancelled - if in-transit, reverses source deductions with transfer_cancelled movements
  • completed orders cannot be cancelled

List Transfer Orders

entityId
string
Filter by entity UUID
JavaScript
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
}
Python
res = requests.get(
    "https://dev.taxmtd.uk/api/transfer-orders",
    params={"entityId": "..."},
    cookies=session_cookies,
)
orders = res.json()["data"]
PHP
$response = Http::withCookies($session)
    ->get('https://dev.taxmtd.uk/api/transfer-orders', ['entityId' => '...']);

$orders = $response->json()['data'];
Rust
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
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.

JavaScript
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")
Python
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"]
PHP
$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'];
Rust
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
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

JavaScript
// 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' }
})
Python
# 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,
)
PHP
// 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']
);
Rust
// 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?;
cURL
# 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.

JavaScript
await $fetch('https://dev.taxmtd.uk/api/transfer-orders', {
  method: 'DELETE',
  body: { id: '...' }
})
// Returns: { data: { success: true } }
Python
res = requests.delete(
    "https://dev.taxmtd.uk/api/transfer-orders",
    json={"id": "UUID"},
    cookies=session_cookies,
)
PHP
$response = Http::withCookies($session)->delete(
    'https://dev.taxmtd.uk/api/transfer-orders',
    ['id' => 'UUID']
);
Rust
let res = client.delete("https://dev.taxmtd.uk/api/transfer-orders")
    .json(&serde_json::json!({"id": "UUID"}))
    .send().await?;
cURL
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 begins
  • in_progress → completed - calculates variance per line, creates stock_take movements, updates warehouse stock to match counted quantities, recalculates product totals
  • draft or in_progress → cancelled
  • completed stock takes cannot be cancelled

List Stock Takes

entityId
string
Filter by entity UUID
JavaScript
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
}
Python
res = requests.get(
    "https://dev.taxmtd.uk/api/stock-takes",
    params={"entityId": "..."},
    cookies=session_cookies,
)
stock_takes = res.json()["data"]
PHP
$response = Http::withCookies($session)
    ->get('https://dev.taxmtd.uk/api/stock-takes', ['entityId' => '...']);

$stockTakes = $response->json()['data'];
Rust
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
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.

JavaScript
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)
Python
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"]
PHP
$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'];
Rust
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
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.

JavaScript
// 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' }
})
Python
# 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,
)
PHP
// 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']
);
Rust
// 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?;
cURL
# 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.

JavaScript
await $fetch('https://dev.taxmtd.uk/api/stock-takes', {
  method: 'DELETE',
  body: { id: '...' }
})
// Returns: { data: { success: true } }
Python
res = requests.delete(
    "https://dev.taxmtd.uk/api/stock-takes",
    json={"id": "UUID"},
    cookies=session_cookies,
)
PHP
$response = Http::withCookies($session)->delete(
    'https://dev.taxmtd.uk/api/stock-takes',
    ['id' => 'UUID']
);
Rust
let res = client.delete("https://dev.taxmtd.uk/api/stock-takes")
    .json(&serde_json::json!({"id": "UUID"}))
    .send().await?;
cURL
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.

entityId
string
Filter by entity UUID
locationId
string
Filter by specific warehouse location UUID
JavaScript
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
}
Python
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"]
PHP
$response = Http::withCookies($session)
    ->get('https://dev.taxmtd.uk/api/reports/stock-valuation', ['entityId' => '...']);

$rows = $response->json()['data'];
$summary = $response->json()['summary'];
Rust
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
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"