PLINTH-API.md
Language Markdown4.2 KB98 lines
# Plinth API referencePlinth is Meridian Display Co.'s order system: orders, customers, the statuscatalog, split shipments, products and stock. Like the company, it isfictional. This is the reference the server in `mcp-server/` is writtenagainst, and the one its code comments point to.## Transport| Item | Value ||---|---|| Endpoint | `https://orders.meridiandisplay.example/api/v2/dispatch` || Style | One endpoint; the action is chosen by the `method` query parameter || HTTP method | GET for reads || Format | JSON || Environment | **Production only.** There is no sandbox; every call reads live data. || Auth | Static key in the `api_key` query parameter on every call, with global **read** scope || Rate limit | 10 requests per 10 seconds, across every caller sharing the key |The key reads every customer's full record, so the API has no per-customerscoping of its own. Anything that exposes its data to someone other than staffhas to verify identity first (see `mcp-server/src/lib/classification.ts`).## Methods| Method | Kind | What it returns ||---|---|---|| `orders.fetch` | read | One order or a batch || `statuses.list` | read | The status catalog (a lookup table) || `customers.fetch` | read | Customer records || `orders.splitShipments` | read | Per-destination breakdown of a multi-address order || `products.fetch` | read | Product specs and turnaround (schema not yet probed) || `inventory.fetch` | read | Stock levels (schema not yet probed) || `orders.setStatus` | **write** | Changes an order's status and can email the customer || `inventory.adjust` | **write** | Changes stock levels |The server never dispatches a write method; see the four-layer guard in`mcp-server/src/lib/guard.ts`.## Rules that hold across methods- **Request dates are `yyyymmdd`**, with no separators. ISO dates are rejectedwith `{"error": "Bad since date"}`.- **Response dates differ by method:** `orders.fetch` returns MySQL`YYYY-MM-DD HH:MM:SS`; `orders.splitShipments` returns plain `YYYY-MM-DD`.- **Two error shapes**, both on HTTP 200: `{"error": "…"}` and`{"notice": "No match"}`. Success bodies never carry either key.- **Collections switch type:** an empty one arrives as `[]`, a populated oneas an object keyed `"1".."N"`.- **Numbers arrive as strings** in some records and as numbers in others.- **Fail-open filters:** a parameter the method doesn't know is ignored, notrejected, so a typo returns the unfiltered set.- **One misspelled key**, `carier_name`, is part of the contract. Map it once.## Method notes**`orders.fetch`**- Parameters:- `order_ref` (a raw integer);- `ref_cmp` (`below` or `above`, relative to `order_ref`);- `customer_ref`, which errors unless paired with both dates;- `shop` and `subshops` (`1` to include child shops);- `status_code`;- `since` and `until`;- `offset` and `limit` (cap 100).- Response: `order_map`, with orders keyed `ord_{order_ref}`. `lines` and`shipments` are ordinal-keyed.- `closed_on` holds the date the order was **placed**, despite its name.**`statuses.list`**- Ignores every filter.- `status_code` is the stable identifier and is what orders carry.- The `row_N` keys are **not** stable: the catalog gained an entry andreshuffled between two pulls. Map by `status_code`.**`customers.fetch`**- Filters on exact `email`, or on a `since`/`until` range over`date_field` = `signup` (default) or `last_login`.- Records are keyed `cust_{n}`.- **There is no phone filter.** A phone parameter is silently dropped and thefull customer list comes back.**`orders.splitShipments`**- The only method whose top level is a bare array, one entry per destination.- `{"notice": "No match"}` for a valid single-address order means "not amulti-address order", not a failure.- `split_lines` can be an array or an ordinal-keyed object within oneresponse.## Order numbersCustomers and staff quote the **display** number, which the API neitheraccepts nor returns:- **Current:** `MD` + the order's two-digit year + `order_ref`. Order 39582placed in 2026 is `MD2639582`.- **Legacy:** `M-` + six digits, from before Plinth. These don't map to an`order_ref` by rule, so route them to a person.