---
title: "Creates a purchase order in `Draft`, optionally with its lines."
method: POST
path: "/api/v1/purchase-orders"
tags: ["PurchaseOrders"]
---

# Creates a purchase order in \`Draft\`, optionally with its lines.

`POST /api/v1/purchase-orders`

## Request

- Base URL: `https://app.flexonthejob.com`
- URL: `https://app.flexonthejob.com/api/v1/purchase-orders`
- Auth: HTTP bearer

Requires `ManagePurchaseOrders` and an `Idempotency-Key` header. Same as "New Purchase
Order" in the app: the vendor must be active in your organization (or omit `vendorId` and
name a one-off vendor), the receiving location must be an active top-level location that is not
a job site, and each line names one of your items or bundles. The order and its lines are
written together or not at all. A new order is a `Draft`: move it along with
`/mark-ordered`, `/mark-shipped` and `/mark-delivered`, then `/receive` it.
Attributed to the key's creator. Example:
`{ "vendorId": 7, "receivingLocationId": 1, "lines": [ { "itemId": 12, "quantityOrdered": 24, "unitPrice": 3.10 } ] }`.

## Request body

- CreatePurchaseOrderApiRequest: Body of `POST /api/v1/purchase-orders`: a new `Draft` order, optionally with its lines. Example: `{ "vendorId": 7, "receivingLocationId": 1, "lines": [ { "itemId": 12, "quantityOrdered": 24, "unitPrice": 3.10 } ] }`.
  - `orderNumber` string, nullable: PO number, up to 50 characters. Omit to have one generated the way the app does: `{vendor name, letters and digits}-{yyyy-MM-dd}-{nnnn}` (e.g. `AcmeSupply-2026-09-22-0001`), or `PO-{yyyy}-{nnnn}` without a vendor.
  - `externalOrderNumber` string, nullable: The vendor's own order or confirmation number, up to 50 characters. Example: `SO-88213`.
  - `vendorId` integer, nullable: An active vendor from `GET /api/v1/vendors`. Omit for a one-off vendor named in `vendorName`. Example: `7`.
  - `vendorName` string, nullable: Vendor name on the order, up to 200 characters. Defaults to the vendor record's name when `vendorId` is given.
  - `vendorContactEmail` string, nullable: Contact email for this order, up to 100 characters. Defaults to the vendor record's contact email.
  - `vendorContactPhone` string, nullable: Contact phone for this order, up to 20 characters. Defaults to the vendor record's contact phone.
  - `orderDate` string, date-time, nullable: Order date (organization-local calendar date, no offset). Defaults to today. Example: `2026-09-22`.
  - `expectedDeliveryDate` string, date-time, nullable: Expected delivery date (organization-local calendar date, no offset); optional. Example: `2026-09-30`.
  - `totalCost` number, double, nullable: Order total as agreed with the vendor, in `currency`; optional. Example: `74.40`.
  - `currency` string, nullable: ISO 4217 currency code, 3 letters. Defaults to `USD`.
  - `receivingLocationId` integer: Where the order will be received: an active, top-level location that is not a job site (see `GET /api/v1/locations?parentLocationId=0`). Example: `1`.
  - `notes` string, nullable: Free-text notes on the order.
  - `lines` PurchaseOrderLineApiRequest[], nullable: Lines to order, 0..200, in display order. More can be added later with `POST /api/v1/purchase-orders/{id}/lines`.
    - `itemId` integer, nullable: Item to order (from `GET /api/v1/items`); not a draft or a sub-unit item. Example: `12`.
    - `bundleTemplateId` integer, nullable: Bundle template to order instead of an item; receiving creates one bundle per unit.
    - `description` string, nullable: Line text, up to 500 characters. Defaults to the item or bundle name.
    - `vendorPartNumber` string, nullable: The vendor's part number, up to 100 characters. Example: `RMX-122-250`.
    - `quantityOrdered` integer: Whole units ordered, at least 1. Example: `24`.
    - `unitPrice` number, double, nullable: Price per unit in the order's currency; becomes each received unit's `Cost`. Optional, not negative. Example: `3.10`.
    - `notes` string, nullable: Free-text note on the line.

## Response `201`

Created; `Location` points at the order, body as `GET /api/v1/purchase-orders/{id}`.

- PurchaseOrderDetailDto: One purchase order with its lines, as returned by `GET /api/v1/purchase-orders/{id}` and by `POST /api/v1/purchase-orders/{id}/receive`.
  - `id` integer: Numeric id used in `/api/v1/purchase-orders/{id}`. Unique within this organization. Example: `412`.
  - `syncId` string, uuid: Stable cross-device identifier (GUID) — also the pagination cursor value. Example: `3c9d1a2b-7e4f-4b6a-8c1d-5e6f7a8b9c0d`.
  - `orderNumber` string: The organization's purchase-order number. Example: `PO-ACME-20260915-01`.
  - `externalOrderNumber` string, nullable: The vendor's own order or confirmation number; null when not recorded. Example: `SO-88213`.
  - `status` string: Order status name, derived by the app from the order's shipments and received quantities. One of `Draft` (no shipment yet), `Ordered`, `Shipped`, `PartialShipped`, `Delivered` (at the receiving location, nothing received yet), `PartialReceived`, `Received` (every unit received and every shipment delivered), `Cancelled`.
  - `vendorId` integer, nullable: Id of the vendor record; null for a one-off vendor typed on the order.
  - `vendorName` string, nullable: Vendor name (the vendor record's name, else the one-off name on the order). Example: `Acme Supply`. Null when neither is set.
  - `orderDate` string, date-time: Order date as entered in the app: the organization's local calendar date, `yyyy-MM-ddT00:00:00` with no UTC offset (the `date-time` format here is a local date, not an RFC 3339 instant; do not convert it between time zones). Example: `2026-09-15T00:00:00`.
  - `expectedDeliveryDate` string, date-time, nullable: Expected delivery date as entered in the app, same local calendar-date format as `orderDate` (no UTC offset). Example: `2026-09-22T00:00:00`. Null when unknown.
  - `receivedDate` string, date-time, nullable: When the order became fully `Received`, ISO-8601 UTC; null until then.
  - `totalCost` number, double, nullable: Order total in `currency` as recorded on the order; null when not calculated. Example: `1249.50`.
  - `currency` string: ISO-4217 currency code of the prices on the order. Example: `USD`.
  - `receivingLocationId` integer: Id of the location stock is received at by default (see `/api/v1/locations`). Example: `10`.
  - `receivingLocationName` string, nullable: Name of `receivingLocationId`. Example: `Main Warehouse`.
  - `lineCount` integer: Number of lines on the order. Example: `3`.
  - `quantityOrdered` integer: Units ordered across all lines. Example: `40`.
  - `quantityReceived` integer: Units received across all lines. Example: `25`.
  - `createdAt` string, date-time: Creation time, ISO-8601 UTC. Example: `2026-09-15T14:05:00Z`.
  - `updatedAt` string, date-time, nullable: Last modification time (including a receipt), ISO-8601 UTC; null when never modified since creation.
  - `notes` string, nullable: Free-text notes on the order. Example: `Deliver to dock 3.` Null when none.
  - `lines` PurchaseOrderLineDto[]: The order lines, in the order they were added.
    - `id` integer: Line id — the `lineId` to pass to `POST /api/v1/purchase-orders/{id}/receive`. Example: `1207`.
    - `itemId` integer, nullable: Item (template) id as used by `/api/v1/items/{id}`; null for a bundle line.
    - `bundleTemplateId` integer, nullable: Bundle template id for a bundle (kit) line; null for an item line.
    - `name` string, nullable: What is ordered: the item or bundle name at read time, else the line description. Example: `Copper 12/2 Romex`.
    - `description` string, nullable: Line description as entered on the order. Example: `Copper 12/2 Romex, 250 ft`.
    - `vendorPartNumber` string, nullable: The vendor's part number; null when not recorded. Example: `RMX-122-250`.
    - `quantityOrdered` integer: Units ordered. Example: `10`.
    - `quantityShipped` integer: Units the vendor has shipped, as recorded on the order. Example: `10`.
    - `quantityReceived` integer: Units received into stock so far. Example: `6`.
    - `quantityRemaining` integer: Units still to receive (`quantityOrdered` minus `quantityReceived`) — the most `receive` accepts for this line. Example: `4`.
    - `unitPrice` number, double, nullable: Unit price in the order's `currency`; becomes the `Cost` property of each received item. Null when not priced. Example: `89.99`.
    - `lineTotal` number, double, nullable: Line total in the order's `currency`; null when not calculated. Example: `899.90`.
    - `shipmentId` integer, nullable: Id of the shipment the line travels on; null when not assigned to a shipment. Received items on a shipment with a shipping cost get `Shipping Cost` and `Total Cost` properties.

## Other responses

- `400`: Malformed body or missing `Idempotency-Key`.
- `401`: Unauthorized
- `403`: Forbidden
- `409`: The key's creator is unknown on this server.
- `422`: Unknown or inactive vendor, unusable receiving location, or a bad line (e.g. `lines[0].itemId`: no active item with this id); the field is listed in `errors`. Nothing was written.
- `429`: Too Many Requests

---

[API](https://skmtc.dev/flexonthejob/apis/flex-api.md) · [All operations](https://skmtc.dev/flexonthejob/apis/flex-api/llms.txt) · [OpenAPI document](https://skmtc.dev/flexonthejob/apis/flex-api/revisions/19c8f4d27acf?raw)
