Ir al contenido

Purchase orders

Esta página aún no está disponible en tu idioma.

The purchase_orders resource holds inventory purchase records. Creating a record immediately adds its line items to stock, as the app’s manual receiving flow does. Use it to receive parts and supplies, retrieve purchase history and confirm quantities on recorded orders. Each record can link to a vendor and a receiving location.

Scope required: purchase_orders.read for list/get, purchase_orders.write for create/receive.

{
"object": "purchase_order",
"id": 412,
"vendor_id": 7,
"vendor_name": "iFixit Parts",
"invoice_number": "INV-2025-8841",
"invoice_date": "2025-11-15",
"status": "received",
"source": "api",
"subtotal": { "amount_cents": 18500, "amount": "185.00", "currency": "USD" },
"tax": { "amount_cents": 1665, "amount": "16.65", "currency": "USD" },
"shipping": { "amount_cents": 999, "amount": "9.99", "currency": "USD" },
"total": { "amount_cents": 21164, "amount": "211.64", "currency": "USD" },
"line_item_count": 3,
"location_id": 2,
"tracking_carrier": null,
"tracking_number": null,
"tracking_last_event": null,
"tracking_estimated_delivery": null,
"notes": "Rush order for holiday week",
"delivered_at": null,
"reconciled_at": "2025-11-15T18:22:00.000Z",
"created_at": "2025-11-15T17:55:00.000Z"
}
FieldTypeDescription
idintegerPurchase order ID
vendor_idinteger|nullLinked vendor ID
vendor_namestring|nullVendor display name (can be set without a vendor_id)
invoice_numberstring|nullSupplier invoice number
invoice_datestring|nullDate on the supplier invoice (YYYY-MM-DD)
statusstringreceived, pending, or reconciled
sourcestringHow the PO was created: manual, api, etc.
subtotalobjectSum of line item costs, money object
taxobjectTax amount, money object
shippingobjectShipping amount, money object
totalobjectsubtotal + tax + shipping, money object
line_item_countintegerNumber of visible line items
location_idinteger|nullReceiving location ID
tracking_carrierstring|nullShipping carrier, if tracked
tracking_numberstring|nullTracking number
tracking_last_eventstring|nullLatest tracking event description
tracking_estimated_deliverystring|nullISO-8601 estimated delivery timestamp
notesstring|nullFree-text notes on the order
delivered_atstring|nullISO-8601 timestamp when marked delivered
reconciled_atstring|nullISO-8601 timestamp when reconciled into inventory
created_atstringISO-8601 creation timestamp

All money fields (subtotal, tax, shipping, total, unit_cost, total_cost) are money objects:

{ "amount_cents": 18500, "amount": "185.00", "currency": "USD" }

All money values in request bodies use *_cents fields (integer cents). The API converts to/from internal dollar-float storage.


GET /purchase_orders/:id and POST /purchase_orders return a detail object that includes the line items:

{
"object": "purchase_order",
"id": 412,
"vendor_id": 7,
"vendor_name": "iFixit Parts",
"status": "received",
"subtotal": { "amount_cents": 18500, "amount": "185.00", "currency": "USD" },
"tax": { "amount_cents": 1665, "amount": "16.65", "currency": "USD" },
"shipping": { "amount_cents": 999, "amount": "9.99", "currency": "USD" },
"total": { "amount_cents": 21164, "amount": "211.64", "currency": "USD" },
"line_item_count": 3,
"location_id": 2,
"notes": null,
"reconciled_at": "2025-11-15T18:22:00.000Z",
"created_at": "2025-11-15T17:55:00.000Z",
"line_items": [
{
"object": "purchase_order_line_item",
"id": 901,
"part_id": 55,
"description": "iPhone 14 Screen Assembly",
"qty": 5,
"qty_received": 5,
"unit_cost": { "amount_cents": 3200, "amount": "32.00", "currency": "USD" },
"total_cost": { "amount_cents": 16000, "amount": "160.00", "currency": "USD" },
"ticket_id": null,
"created_at": "2025-11-15T17:55:00.000Z"
}
]
}

Line item visibility: line items linked to hidden or soft-deleted tickets are excluded from line_items and not counted in line_item_count.

Line item fieldTypeDescription
idintegerLine item ID
part_idinteger|nullLinked product/part ID
descriptionstring|nullLine item description
qtyintegerQuantity ordered
qty_receivedinteger|nullQuantity actually received (set during reconciliation)
unit_costobjectCost per unit, money object
total_costobjectqty × unit_cost, money object
ticket_idinteger|nullTicket this line item is linked to, if any
created_atstringISO-8601 creation timestamp

GET /api/v1/purchase_orders

Returns a cursor-paginated list of purchase orders, ordered by ID ascending.

Scope required: purchase_orders.read

ParameterTypeDescription
vendor_idintegerFilter by vendor ID (positive integer ≤ 2,147,483,647; out-of-range values return 400 invalid_query)
statusstringFilter by status: received, pending, reconciled
location_idintegerFilter by receiving location ID (positive integer ≤ 2,147,483,647; out-of-range values return 400 invalid_query)
qstringSearch invoice number or vendor name (substring match)
limitintegerPage size, 1–100 (default: 20)
cursorstringOpaque cursor from a previous response’s next_cursor
Terminal window
curl "https://app.benchkey.com/api/v1/purchase_orders?status=received&limit=20" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
const res = await fetch(
"https://app.benchkey.com/api/v1/purchase_orders?status=received&limit=20",
{ headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } }
);
const { data, has_more, next_cursor } = await res.json();
{
"object": "list",
"data": [
{
"object": "purchase_order",
"id": 412,
"vendor_id": 7,
"vendor_name": "iFixit Parts",
"invoice_number": "INV-2025-8841",
"status": "received",
"subtotal": { "amount_cents": 18500, "amount": "185.00", "currency": "USD" },
"tax": { "amount_cents": 1665, "amount": "16.65", "currency": "USD" },
"shipping": { "amount_cents": 999, "amount": "9.99", "currency": "USD" },
"total": { "amount_cents": 21164, "amount": "211.64", "currency": "USD" },
"line_item_count": 3,
"location_id": 2,
"created_at": "2025-11-15T17:55:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}

See Pagination for how to page through results.


GET /api/v1/purchase_orders/:id

Returns a single purchase order with its full line item list.

Scope required: purchase_orders.read

Terminal window
curl "https://app.benchkey.com/api/v1/purchase_orders/412" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
const res = await fetch(
"https://app.benchkey.com/api/v1/purchase_orders/412",
{ headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } }
);
const order = await res.json();

Returns the purchase order detail object. Returns 404 if not found, 400 if :id is not a valid integer.


POST /api/v1/purchase_orders

Creates an inventory purchase record and immediately commits the line items to inventory stock, the equivalent of the manual receiving flow in the BenchKey app. Returns 201 with the full detail object on success.

Scope required: purchase_orders.write

FieldTypeRequiredDescription
itemsarrayYesLine items to receive (at least one)
vendor_idintegerNoLink to an existing vendor
vendor_namestringNoVendor display name (used when vendor_id is absent or for display override)
invoice_numberstringNoSupplier invoice number
invoice_datestringNoInvoice date (YYYY-MM-DD)
location_idintegerNoReceiving location
tax_centsinteger ≥ 0NoTax amount in integer cents
shipping_centsinteger ≥ 0NoShipping amount in integer cents
notesstringNoFree-text notes
FieldTypeRequiredDescription
part_idintegerNo*Existing product/part ID. Either part_id or name is required
namestringNo*Part name. Required when part_id is not provided, creates a new part if no SKU match is found
skustringNoPart SKU (used to match an existing part when part_id is absent)
descriptionstringNoLine item description (defaults to part name)
qtyinteger ≥ 1YesQuantity to receive
unit_cost_centsinteger ≥ 0YesUnit cost in integer cents
ticket_idinteger ≥ 1NoLink this line item to a ticket (must be a positive integer). The ticket must exist and be visible (not hidden or soft-deleted); a nonexistent or hidden ticket returns 404 not_found
Terminal window
curl -X POST "https://app.benchkey.com/api/v1/purchase_orders" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \
-H "Content-Type: application/json" \
-d '{
"vendor_id": 7,
"invoice_number": "INV-2025-8841",
"invoice_date": "2025-11-15",
"location_id": 2,
"tax_cents": 1665,
"shipping_cents": 999,
"items": [
{ "part_id": 55, "qty": 5, "unit_cost_cents": 3200 },
{ "name": "USB-C Charging Port", "sku": "UC-PORT-001", "qty": 10, "unit_cost_cents": 850 }
]
}'
const res = await fetch("https://app.benchkey.com/api/v1/purchase_orders", {
method: "POST",
headers: {
Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa",
"Content-Type": "application/json",
},
body: JSON.stringify({
vendor_id: 7,
invoice_number: "INV-2025-8841",
invoice_date: "2025-11-15",
location_id: 2,
tax_cents: 1665,
shipping_cents: 999,
items: [
{ part_id: 55, qty: 5, unit_cost_cents: 3200 },
{ name: "USB-C Charging Port", sku: "UC-PORT-001", qty: 10, unit_cost_cents: 850 },
],
}),
});
const order = await res.json(); // 201, full detail object with line_items

Returns 201 with the purchase order detail object.


POST /api/v1/purchase_orders/:id/receive

Marks a purchase order received and optionally reconciles per-line quantities against what was actually delivered. If you omit the items array (or send an empty body), all line items are accepted at their originally ordered quantities.

Use this endpoint to receive an existing pending order, such as one saved with Record order in the app, and confirm the quantities that actually arrived.

Scope required: purchase_orders.write

ParameterDescription
idPurchase order ID
{
"items": [
{ "id": 901, "qty_received": 4 },
{ "id": 902, "qty_received": 10 }
]
}
FieldTypeRequiredDescription
itemsarrayNoPer-line overrides. Omit entirely (or omit the field) to accept all lines at ordered quantities. Supplying items: [] (an empty array) returns 400 invalid_field, if you intend to receive all, omit the field
items[].idintegerYes (per item)Line item ID
items[].qty_receivedinteger ≥ 0NoActual quantity received; omit to use ordered qty
items[].reconcile_notestringNoOptional note for this line (max 5,000 characters)
Terminal window
curl -X POST "https://app.benchkey.com/api/v1/purchase_orders/412/receive" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "id": 901, "qty_received": 4 }
]
}'
const res = await fetch(
"https://app.benchkey.com/api/v1/purchase_orders/412/receive",
{
method: "POST",
headers: {
Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa",
"Content-Type": "application/json",
},
body: JSON.stringify({
items: [{ id: 901, qty_received: 4 }],
}),
}
);
const order = await res.json(); // updated detail object

Returns 200 with the updated purchase order detail object. Returns 404 if the PO does not exist.


HTTP statusCodeMeaning
400invalid_id:id path parameter is not a valid positive integer
400missing_fieldRequired field absent (items array missing, or unit_cost_cents omitted from a line item)
400invalid_fieldA field has an invalid type or value (bad vendor_id, fractional unit_cost_cents, part_id not a positive integer, ticket_id not a positive integer, supplied part_id does not match an active product, items: [] on the receive endpoint, or location_id references an inactive location)
400invalid_queryA list filter parameter was supplied as an array or object instead of a single scalar value
400invalid_cursorcursor is malformed
400invalid_itemAn element of the items array is not a valid object
403insufficient_scopeAPI key lacks the required scope
404not_foundPurchase order not found; vendor_id does not exist; or a line item’s ticket_id references a hidden or soft-deleted ticket
422create_failedThe internal create operation failed (check message for details)
422receive_failedThe internal receive/reconcile operation failed

See Errors for the full error envelope format.

Estado del sistema