---
title: Medicines and Batches
description: Search medicines with per-branch stock, manage the medicine record, read movements, and query the controlled registry.
---

You manage every sellable item through the medicine record in `apps/server/src/features/medicines/route.ts`. Stock itself lives in batches, and the medicine row carries the pricing and safety flags you need at the counter.

### Search medicines with per-branch stock

Call `GET /medicines` with `search`, `category`, `form`, `low_stock`, `is_active`, and `branch_id`. Pass `branch_id` when you need sellable stock for one branch: the query sums the `stock_movements` ledger for that branch, as implemented by `sumStockByMedicine` in `apps/server/src/features/medicines/stock.ts`.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/api/v1/medicines?search=amox&branch_id=$BRANCH_ID&page=1&page_size=20"
```

Filter inactive records with `is_active=all` or `is_active=false`. Combine `low_stock=true` with `branch_id` to scope the threshold check to that branch. Without `branch_id`, the sum covers the whole organisation because there is no running stock column on the medicine row.

### Read one medicine and its movements

Fetch `GET /medicines/:medicineId` for the full record, then `GET /medicines/:medicineId/movements` for its append-only ledger with the acting user joined in. Use the movements list when you need to explain a quantity change to a pharmacist.

```bash
curl -H "Authorization: Bearer $TOKEN" "http://localhost:8000/api/v1/medicines/$MEDICINE_ID"
curl -H "Authorization: Bearer $TOKEN" "http://localhost:8000/api/v1/medicines/$MEDICINE_ID/movements?page=1&page_size=50"
```

### Create and update medicines

You need `admin`, `inventory_manager`, or `branch_manager` to write. The body schema lives in `apps/server/src/features/medicines/schemas.ts` and accepts `name`, `generic_name`, `barcode`, `category`, `form`, `strength`, `unit`, `selling_price`, `low_stock_threshold`, `requires_prescription`, `is_controlled`, `vat_category`, and `requires_cold_chain`.

```bash
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Amoxicillin 500mg","generic_name":"Amoxicillin","category":"Antibiotic","form":"capsule","strength":"500mg","unit":"capsule","selling_price":"12.500","low_stock_threshold":20,"requires_prescription":true,"vat_category":"zero_rated"}' \
  "http://localhost:8000/api/v1/medicines"
```

Updates accept the same fields as partial input; only keys listed in `updatableColumns` are written. Deleting a medicine is a soft delete that sets `is_active = 0` and emits a sync `DELETE` event. Product photos are never set here; they go through the validated image endpoint only.

### Handle controlled medicines

Flag a record with `is_controlled: true` at creation. Every controlled dispense is written to `controlled_dispense_log` at sale time, and you read it back through `GET /medicines/controlled/registry`, restricted to `admin`, `auditor`, `branch_manager`, and `pharmacist`. Filter with `medicine_id` and `patient_id` when you audit one drug or one patient.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/api/v1/medicines/controlled/registry?medicine_id=$MEDICINE_ID&page=1&page_size=50"
```

### Avoid common errors

You get `404 Medicine not found` when the id is wrong or the record is soft-deleted, and `400 No fields to update` when your `PUT` body carries nothing writable. Send money as a decimal string such as `"12.500"` because `formatMedicine` in `apps/server/src/features/medicines/catalog/queries.ts` returns prices that way. Keep `barcode` unique per product so counter search resolves to one row.

```bash
curl -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"selling_price":"13.000","low_stock_threshold":25}' \
  "http://localhost:8000/api/v1/medicines/$MEDICINE_ID"
```

Check `is_active` on the response after every write so you never dispense a deactivated record.

### Next steps

Continue with `/inventory/purchasing-and-purchase-orders` to receive stock into batches, then check `/inventory/alerts-and-expiry` to watch thresholds and expiry for the medicines you just created.
