# WFDASH API — Create Order (POST /api/v1/orders/create) > **One payload, one call.** The full order header + items array goes > in a single request body. There is no `/api/v1/orders` POST — > `/orders` is GET-only. --- ## Endpoint ``` POST /api/v1/orders/create ``` Bearer-token auth via `AGENT_API_KEY` header. **Hermes drops `AGENT_API_KEY` on direct REST calls** — use the MCP wrapper `mcp__wfdash-mcp__orders__create` with the same body shape. `runtime = "nodejs"` (PocketBase admin auth requires Node). --- ## Request body ```json { "customer_id": "<15-char PB id>", // required "company_id": "<15-char PB id>", // required "notes": "string?", // order-level notes "contact_name":"string?", // operator-readable contact "description": "string?", // short description of the order "email_message_id": "string?", // dedup key "rep": "string?", // sales rep free-text "items": [ { "description": "string", // required, trimmed "quantity": 1, // default 1; must be > 0 when supplied "cost_price": 123.45, "markup": 25, "selling_price": 154.31, // optional "sku": "string?", "unit": "EA", "supplier_id": "<15-char PB id?>", "notes": "string?" } ] } ``` ### Field rules - **`customer_id`, `company_id`** — must resolve in PB. Free-text names are **not** accepted on this endpoint (use the MCP `orders.create` wrapper if you need name-resolution; the REST handler requires ids). - **`items[]`** — non-empty, every item needs `description`. - **`selling_price` auto-derive** — when `cost_price` and `markup` are both supplied and `selling_price` is omitted, it auto-derives as `cost_price * (1 + markup / 100)` rounded to 2 dp. Pass `selling_price` explicitly to override. - **`email_message_id`** — optional idempotency key. If an order with the same `email_message_id` already exists, the response returns the existing record with `duplicate: true` and HTTP **200** instead of 201. Safe to retry. - **Per-row independence** — each item creates one `order_item` row with `item_status = "pending"` and `drv_status = "unassigned"`. One item's secondary-field patch failure does **not** abort the rest of the call; failures land in `items_failed[]` with the partial-success shape. --- ## Response ### 201 — created ```json { "order": { /* full PB order record */ }, "order_items_collection": "", "items_created": [ { "id": "...", "description": "..." } ], "items_failed": [] } ``` ### 200 — duplicate (`email_message_id` matched) Same shape as 201, plus `"duplicate": true`. Nothing new was written. ### 400 — validation failure ```json { "error": "customer_id is required" } { "error": "items must be a non-empty array" } ``` ### 404 — not found ```json { "error": "Requested resource not found..." } ``` `customer_id` or `company_id` doesn't resolve in PB. ### 500 — server / PB write failure ```json { "error": "" } ``` --- ## Common workflows ### Walk-in / counter sale No upstream quote. Customer walks up, picks items, you write the order straight from the till: ```bash curl -X POST https://wfdash.example/api/v1/orders/create \ -H "Authorization: Bearer $AGENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "", "company_id": "", "notes": "Counter sale — 2026-08-27", "items": [ { "description": "SKF 6205 bearing", "quantity": 4, "cost_price": 85, "markup": 30 } ] }' ``` ### Re-create from a quote **Don't** use this endpoint — use `POST /api/v1/orders/from-quote` (MCP: `orders.create_from_quote`). It clones the matched quote into a fresh `ORD-YYYY-MM-NNN` order, inheriting customer / company / rep / letterhead / line items. ### Append to an existing order There **is** a separate endpoint: `POST /api/v1/orders/{id}/items` (MCP: `order_items.create`). But it **refuses with 409 `order_locked`** the moment any line on the order is past `pending`. Use it only on fresh orders — for any order that has a PO in flight, you must `orders.split` first. --- ## Pitfalls - **Passing the order header without `items[]`** → 400. The handler does not create empty orders. - **Passing `quantity: 0` or negative** → rejected per item by the service layer; the row lands in `items_failed[]` with `validation_error`. - **Forgetting to send `AGENT_API_KEY`** → 401 from `assertAgentAuth`. From Hermes, use `mcp__wfdash-mcp__orders__create` instead — the MCP layer injects the key. - **Reusing `email_message_id` for genuinely different orders** → the second call returns the first order with `duplicate: true`. Don't reuse the key; generate a new one per logical order.