Appearance
Purchase orders
8 reads and 18 writes, on train 2026-11. Scopes: purchase_orders:read · purchase_orders:write.
| Operation | Method | Scope | Path |
|---|---|---|---|
| List purchase orders | GET | purchase_orders:read | /api/v1/organizations/{organization_id}/purchase-orders |
| Open a purchase order | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders |
| Delete Purchase Order | DELETE | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id} |
| Get purchase order | GET | purchase_orders:read | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id} |
| Update purchase order | PATCH | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id} |
| Approve a purchase order | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/approve |
| Reserve the raw materials a manufacturing order needs | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/auto-allocate |
| What this order would draw from its Commitments | GET | purchase_orders:read | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commitment-draw |
| Get purchase order deliveries | GET | purchase_orders:read | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries |
| Create purchase order delivery | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries |
| Remove a delivery recorded in error | DELETE | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/{delivery_id} |
| One recorded delivery against a purchase order | GET | purchase_orders:read | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/{delivery_id} |
| Correct a recorded delivery | PATCH | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/{delivery_id} |
| Record several deliveries against one purchase order in a single call | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/bulk |
| Duplicate purchase order | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/duplicate |
| Export purchase order | GET | purchase_orders:read | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/export |
| Stop an order and say why | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/hold |
| Send the purchase order to its supplier by their channel | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/issue |
| Close an order out by recording everything outstanding as delivered | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/mark_delivered |
| Create or amend an inbound at the warehouse | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/push-to-wms |
| Create many purchase orders in one call | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/bulk |
| List purchase order filters | GET | purchase_orders:read | /api/v1/organizations/{organization_id}/purchase-orders/filters |
| Create purchase orders from basket | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/from-basket |
| Update purchase order generation settings | PATCH | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/generation-settings |
| Create manufacturing orders in bulk | POST | purchase_orders:write | /api/v1/organizations/{organization_id}/purchase-orders/manufacturing/bulk |
| List purchase orders with pending supplier updates | GET | purchase_orders:read | /api/v1/organizations/{organization_id}/purchase-orders/with-supplier-updates |
List purchase orders
GET /api/v1/organizations/{organization_id}/purchase-orders
Scopes: purchase_orders:read
A page of purchase orders for one organisation: number, order type, status, supplier, destination location, order and expected delivery dates, total cost and line count, with filtered_max_size for the size of the filtered set. Header figures only, so call Get purchase order for one order's line quantities and its deliveries.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
| Optional | In | What it is |
|---|---|---|
limit | query | Pagination limit (defaults to 10 when neither limit nor offset is provided) |
offset | query | Pagination offset |
filter_args | query | JSON array of filter conditions. |
sort_args | query | Comma-separated list of sort arguments. |
search | query | Search term to filter purchase orders |
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"filtered_max_size": 214,
"offset": 0,
"rows": [
{
"expected_delivery_date": "2026-10-02",
"external_id": "PO-9014",
"hold_reason": "Vendor has not confirmed the navy; do not issue until they do.",
"id": "9014",
"location_id": "loc_0004",
"name": "PO-9014 Porto Knits",
"on_hold": true,
"order_date": "2026-09-04",
"order_type": "purchase",
"status": "issued",
"supplier_id": "sup_0031",
"supplier_name": "Porto Knits",
"total_cost": 148200,
"total_line_items": 12
}
],
"size": 1
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 Bad Request - Invalid request parameters or validation errors
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot read Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 429
Open a purchase order
POST /api/v1/organizations/{organization_id}/purchase-orders
Scopes: purchase_orders:write
Opens one purchase order and returns it. The body names the destination location_id (required) and, optionally, order_type, supplier_id, source_location_id, recommended_quantity, order_date, expected_delivery_date and commitment_id.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"cancel_date": "2026-01-15",
"expected_delivery_date": "2026-01-15",
"location_id": "<location_id>",
"order_date": "2026-01-15",
"ship_window_start": "2026-01-15"
}What it answers
json
{
"data": {
"expected_delivery_date": "2026-10-02",
"external_id": "PO-9014",
"id": "9014",
"location_id": "loc_0004",
"name": "PO-9014 Porto Knits",
"order_type": "purchase",
"supplier_id": "sup_0031",
"total_line_items": 0
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 Bad Request - Invalid request parameters or validation errors. Also returned when a stamp names a Commitment this organization does not have, and when a TRANSFER order draws more of a variant than its source location holds. Nothing is created in either case.
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 429
Delete Purchase Order
DELETE /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}
Scopes: purchase_orders:write
Deletes one purchase order and its line items, and answers 204 with no body. Nothing is kept in its place, so use Stop an order and say why to stop an order that may come back, or a cancel through Update purchase order to keep it and its trail.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | Unique identifier for the purchase order to delete |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X DELETE "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
204, with no body. The purchase order is deleted. No body
What it refuses
- 400 Bad Request - Invalid request parameters or validation errors
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 Purchase order not found
- 429
Get purchase order
GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}
Scopes: purchase_orders:read
One order in full: the header (supplier, destination location, order and expected delivery dates, status, currency, total cost), every line item with its ordered, confirmed and delivered quantities and unit cost, and the deliveries recorded against it.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | Unique identifier for the purchase order |
| Optional | In | What it is |
|---|---|---|
sort_args | query | Comma-separated list of sort arguments for line items. |
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"container_plan": {
"basis": "lane",
"binds": "cube",
"container_type_id": 41,
"container_type_name": "40' HC",
"containers": 1,
"cube_pct": 34.6,
"fingerprint": "3f0c9e2b7d5a4c1e8b6f0a9d2c4e6f8a1b3d5c7e9f0a2b4c6d8e0f1a3b5c7d9e",
"freight_per_container_cents": 718500,
"freight_total_cents": 718500,
"gross_kg": 3180,
"lines": [
{
"cartons": 40,
"cbm": 22.32,
"hs_code": "6110.20.2079",
"kg": 3180,
"landed_reason": null,
"landed_unit_cost": 15.39,
"variant_id": "44100920011"
}
],
"total_cbm": 22.32,
"unmeasured_lines": 0,
"usable_cbm_total": 64.6,
"weight_pct": 12
},
"currency": "USD",
"expected_delivery_date": "2026-10-02",
"external_id": "PO-9014",
"hold_reason": "Vendor has not confirmed the navy; do not issue until they do.",
"id": "9014",
"line_items": [
{
"cartons": 40,
"cbm": 22.32,
"delivered_quantity": 0,
"hs_code": "6110.20.2079"
…
}
]
}
}What it refuses
- 400 Bad Request - Invalid sort column or validation error
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot read Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 Purchase order not found
- 429
Update purchase order
PATCH /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}
Scopes: purchase_orders:write
Patches one order: its status, its dates, its notes, and its line items' quantities and costs. Line items go as [{"id": 123, "quantity": 40, "confirmed_quantity": 50, "unit_cost": 4.99}], where id is the line item's id; shipped_quantity and delivered_quantity are accepted the same way. is_billed: true requires billed_at (YYYY-MM-DD) in the same request.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | Unique identifier for the purchase order to update |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X PATCH "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"additional_costs": [
{}
],
"billed_at": "2026-01-15",
"cancel_date": "2026-01-15",
"comment": "<comment>",
"commercial_resolution_id": "<commercial_resolution_id>",
"commitment_id": "<commitment_id>",
"exceptions": [
"<exceptions>"
],
"expected_delivery_date": "2026-01-15",
"filter_args": [
{
"key": "<key>",
"operation": "<operation>"
}
],
"include_planned_to_deliver": false,
"line_items": [
{}
],
"ship_window_start": "2026-01-15",
"should_update_inventory": false
}What it answers
json
{
"data": {
"expected_delivery_date": "2026-10-09",
"id": "9014",
"name": "PO-9014 Porto Knits",
"status": "issued",
"total_cost": 148200
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 Bad Request - Invalid request parameters or validation errors
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 Purchase order not found
- 409 Conflict - the order's state refuses this change. Cancelling an order that is synced with Xero and still carries an open bill is refused here; so is advancing an order that is on hold. Nothing was written.
- 429
Approve a purchase order
POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/approve
Scopes: purchase_orders:write
Confirms a drafted order, moving it to FullyConfirmed through the same path the manual confirm takes, and records who approved it and why on the order's audit trail. The body's reason and via are optional, and via defaults to "api". The response carries the confirmed order and the audit entry that was appended.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order to approve. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/approve" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"reason": "<reason>",
"via": "<via>"
}What it answers
json
{
"data": {
"audit": {
"actor": "66c1f0a2e4b09a3d5c7f1a02",
"at": "2026-09-04T09:12:00+00:00",
"event": "approved",
"reason": "Buying plan signed off for October intake",
"via": "api"
},
"purchase_order": {
"expected_delivery_date": "2026-10-02",
"id": "9014",
"name": "PO-9014 Porto Knits",
"status": "FullyConfirmed",
"total_cost": 148200
}
}
}What it refuses
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 409 Already confirmed, on hold, or refused by the OTB guardrail / commitment envelope
- 429
Reserve the raw materials a manufacturing order needs
POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/auto-allocate
Scopes: purchase_orders:write
Walks one manufacturing order's bill of materials and reserves the components available at its source location. The response names, per component, the quantity required, the quantity allocated and the shortfall, with fully_allocated for the whole order.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/auto-allocate" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"allocated": [
{
"allocated_quantity": 480,
"required_quantity": 480,
"shortfall": 0,
"variant_id": "44100000021"
},
{
"allocated_quantity": 180,
"required_quantity": 240,
"shortfall": 60,
"variant_id": "44100000022"
}
],
"fully_allocated": false,
"purchase_order_id": 9015
}
}What it refuses
- 400 The order is not a manufacturing order, or has no bill of materials to walk.
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 No such purchase order, or no such delivery on it, for this organisation.
- 429
What this order would draw from its Commitments
GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commitment-draw
Scopes: purchase_orders:read
What this order WOULD draw from the Commitments its lines belong to, measured per line: one group per Commitment with the draw and the headroom left, the lines no Commitment covers with the reason, and would_be_refused with the sentence a refusal would carry.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/commitment-draw" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"as_of": "2026-09-04",
"clear_lines": {
"count": 9,
"grain": "line",
"population": "lines that would be allowed"
},
"currency": "USD",
"groups": [
{
"commitment_id": "cmt_0091",
"commitment_name": "SS27 Knitwear buy",
"draw_usd": 148200,
"headroom_usd": 260000,
"lines": 9,
"would_be_refused": false
}
],
"lines": {
"count": 12,
"grain": "line",
"population": "lines on this order"
},
"projection_note": "Nothing here has been drawn. The envelope moves when the order is placed.",
"purchase_order_id": 9014,
"purchase_order_name": "PO-9014 Porto Knits",
"refusal_reason": null,
"refused_lines": {
"count": 0,
"grain": "line",
"population": "lines that would be refused"
},
"status": "draft",
"unattributed": {
"lines": 3,
"reason": "no Commitment declares these items in this window",
"value_usd": 18400
},
"would_be_refused": false
…
}
}What it refuses
- 400 The purchase order id is not a number.
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
plan_excludes: "This organisation's plan does not include Commitments. It is sold with Pro." The key may not reach this operation.plan_excludeswhen this organisation's plan does not include Commitments, which is sold with Pro and is what gates this read rather than the rest of Purchase orders;scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 No such purchase order, or no such delivery on it, for this organisation.
- 429
Get purchase order deliveries
GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries
Scopes: purchase_orders:read
Every delivery recorded against one order, each with the date the goods arrived, the date they were expected, and its line items' delivered and expected quantities. An order carries as many partial deliveries as it needs before it is complete. One recorded delivery against a purchase order is the read for one of them by id.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order ID |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/deliveries" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": [
{
"delivery_date": "2026-09-18",
"delivery_line_items": [
{
"delivered_quantity": 240,
"expected_quantity": 240,
"variant_id": "44100920011"
},
{
"delivered_quantity": 180,
"expected_quantity": 200,
"variant_id": "44100920012"
}
],
"expected_delivery_date": "2026-09-21",
"id": "8841"
}
],
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot read Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 Purchase order not found
- 429
Create purchase order delivery
POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries
Scopes: purchase_orders:write
Records that goods from one order have been received, and moves stock. The body carries delivery_date (the day they arrived; omit it for a delivery that is still expected), an optional expected_delivery_date, and delivery_line_items as [{"variant_id": "44100920011", "delivered_quantity": 240}], where expected_quantity is optional and defaults to the order line's quantity. The response is the delivery as recorded.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order ID |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/deliveries" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"delivery_date": "2026-01-15",
"delivery_line_items": [
{
"delivered_quantity": 1,
"expected_quantity": 1,
"variant_id": "<variant_id>"
}
],
"expected_delivery_date": "2026-01-15"
}What it answers
json
{
"data": {
"delivery_date": "2026-09-18",
"delivery_line_items": [
{
"delivered_quantity": 240,
"expected_quantity": 240,
"variant_id": "44100920011"
},
{
"delivered_quantity": 180,
"expected_quantity": 200,
"variant_id": "44100920012"
}
],
"exceptions_opened": [
{
"id": 4412,
"kind": "exception_short_receipt",
"title": "PO-00001042 arrived 20 units short: 180 of 200 MAR-TOP-L."
}
],
"expected_delivery_date": "2026-09-21",
"id": "8841",
"movements": [
{
"id": 100241,
"quantity_delta": 240,
"variant_id": "44100920011"
},
{
"id": 100242,
"quantity_delta": 180,
"variant_id": "44100920012"
}
]
},
"message": {
"desc": "",
"service": "purchase_order"
…
}
}What it refuses
- 400 Bad Request - invalid delivery data.
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 Purchase order not found
- 429
Remove a delivery recorded in error
DELETE /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/{delivery_id}
Scopes: purchase_orders:write
Deletes one recorded delivery and its lines, answers 204, and the order's delivered quantity drops by what that delivery carried.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order. |
delivery_id | path | The delivery on that order. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X DELETE "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/deliveries/<delivery_id>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
204, with no body. Deleted. No body.
The units come back before the document goes: a movement is never rewritten, so removing a receipt writes a full reversal per line against the delivery while it still exists, and the ledger foots to zero for it.
What it refuses
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 No such purchase order, or no such delivery on it, for this organisation.
- 409 "The receipt of Sep 18, 2026 on PO-00001042 was billed as BILL-4471 in Xero on Sep 19, 2026; void the bill there before changing or removing the receipt."
delivery_has_posted_billwhen the receipt has already been billed to Xero or QuickBooks. Void the bill in the ledger before changing or removing the receipt. - 429
One recorded delivery against a purchase order
GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/{delivery_id}
Scopes: purchase_orders:read
One recorded delivery: its dates, and its line items with what was expected against what arrived. Use Get purchase order deliveries for every delivery on the order; this is the read for one of them by id.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order. |
delivery_id | path | The delivery on that order. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/deliveries/<delivery_id>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"delivery_date": "2026-09-18",
"delivery_line_items": [
{
"delivered_quantity": 240,
"expected_quantity": 240,
"variant_id": "44100920011"
},
{
"delivered_quantity": 180,
"expected_quantity": 200,
"variant_id": "44100920012"
}
],
"expected_delivery_date": "2026-09-21",
"id": "8841"
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot read Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 No such purchase order, or no such delivery on it, for this organisation.
- 429
Correct a recorded delivery
PATCH /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/{delivery_id}
Scopes: purchase_orders:write
Replaces one recorded delivery's dates and line items with what is sent. This is the correction path for a receipt already recorded: a miscount, a date typed wrongly, a line that arrived later.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order. |
delivery_id | path | The delivery on that order. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X PATCH "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/deliveries/<delivery_id>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"delivery_date": "2026-01-15",
"delivery_line_items": [
{
"delivered_quantity": 1,
"expected_quantity": 1,
"variant_id": "<variant_id>"
}
],
"expected_delivery_date": "2026-01-15"
}What it answers
json
{
"data": {
"delivery_date": "2026-09-18",
"delivery_line_items": [
{
"delivered_quantity": 240,
"expected_quantity": 240,
"variant_id": "44100920011"
},
{
"delivered_quantity": 180,
"expected_quantity": 200,
"variant_id": "44100920012"
}
],
"exceptions_opened": [],
"expected_delivery_date": "2026-09-21",
"id": "8841",
"movements": [
{
"id": 100243,
"quantity_delta": -20,
"variant_id": "44100920011"
}
]
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 A quantity is negative, or a date could not be read.
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 No such purchase order, or no such delivery on it, for this organisation.
- 409 "The receipt of Sep 18, 2026 on PO-00001042 was billed as BILL-4471 in Xero on Sep 19, 2026; void the bill there before changing or removing the receipt."
delivery_has_posted_billwhen the receipt has already been billed to Xero or QuickBooks. Void the bill in the ledger before changing or removing the receipt. - 429
Record several deliveries against one purchase order in a single call
POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/bulk
Scopes: purchase_orders:write
Records several deliveries against one order in a single call, for a warehouse posting a day's receipts at once. Each entry is one delivery in the shape Create purchase order delivery takes, and the response carries them as recorded.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/deliveries/bulk" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"deliveries": [
{
"delivery_date": "2026-01-15",
"delivery_line_items": [
{}
],
"expected_delivery_date": "2026-01-15"
}
]
}What it answers
json
{
"data": [
{
"delivery_date": "2026-09-18",
"delivery_line_items": [
{
"delivered_quantity": 240,
"expected_quantity": 240,
"variant_id": "44100920011"
},
{
"delivered_quantity": 180,
"expected_quantity": 200,
"variant_id": "44100920012"
}
],
"exceptions_opened": [],
"expected_delivery_date": "2026-09-21",
"id": "8841",
"movements": []
}
],
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 A quantity is negative, a date could not be read, or no delivery was sent.
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 No such purchase order, or no such delivery on it, for this organisation.
- 429
Duplicate purchase order
POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/duplicate
Scopes: purchase_orders:write
Copies one order with all its line items and returns the copy. The copy is a new draft with its own id and number and no expected delivery date; the original is untouched.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | Unique identifier for the purchase order to duplicate |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/duplicate" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"expected_delivery_date": null,
"external_id": "PO-9016",
"id": "9016",
"location_id": "loc_0004",
"name": "PO-9016 Porto Knits (copy)",
"order_type": "purchase",
"supplier_id": "sup_0031",
"total_line_items": 12
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 Bad Request - Invalid request parameters or validation errors
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 Purchase order not found
- 429
Export purchase order
GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/export
Scopes: purchase_orders:read
Renders one order as a file and answers with a URL to download it. format is required and takes pdf or csv; fields names the columns a CSV carries and the order they appear in. The URL is presigned and short-lived, so fetch it rather than storing it.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | Unique identifier for the purchase order to export |
format | query | The file the export is rendered as. Defaults to PDF. |
| Optional | In | What it is |
|---|---|---|
fields | query | Comma-separated columns to export. |
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/export?format=<format>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"url": "https://files.tightly.io/exports/po-9014.pdf"
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 Bad Request - Invalid request parameters or validation errors
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot read Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 Purchase order not found
- 429
Stop an order and say why
POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/hold
Scopes: purchase_orders:write
Puts one order on hold, or lifts a standing hold with release: true. A held order refuses every status advance until it is released; cancelling stays allowed. reason is required when holding, and is recorded on the order and on its audit trail. The response carries the order's hold state, its reason and the audit entry.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order to put on hold. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/hold" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"reason": "<reason>",
"release": false,
"via": "<via>"
}What it answers
json
{
"data": {
"audit": {
"actor": "66c1f0a2e4b09a3d5c7f1a02",
"at": "2026-09-04T09:14:00+00:00",
"event": "held",
"reason": "Waiting on the supplier's revised delivery date",
"via": "api"
},
"hold_reason": "Waiting on the supplier's revised delivery date",
"on_hold": true,
"purchase_order_id": "9014"
}
}What it refuses
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 409 Already on hold, or not on hold when releasing
- 429
Send the purchase order to its supplier by their channel
POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/issue
Scopes: purchase_orders:write
Sends the order to its supplier: exports the PDF and emails it to the supplier's main contact, or to to_email when the body names one, through the organisation's connected email account, then records the issue on the audit trail. message replaces the default one-line cover note. The response says who it went to and by which channel.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order to issue to its supplier. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/issue" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"message": "<message>",
"to_email": "<to_email>",
"via": "<via>"
}What it answers
json
{
"data": {
"audit": {
"actor": "66c1f0a2e4b09a3d5c7f1a02",
"at": "2026-09-04T09:20:00+00:00",
"event": "issued",
"reason": "emailed to orders@portoknits.example",
"via": "api"
},
"channel": "email",
"issued_to": "orders@portoknits.example",
"purchase_order_id": "9014"
}
}What it refuses
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 409 Wrong order type, cancelled, on hold, or no recipient resolvable
- 429
Close an order out by recording everything outstanding as delivered
POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/mark_delivered
Scopes: purchase_orders:write
Closes an order out: every line still outstanding is recorded as delivered on one date, rather than typed in line by line. delivery_date defaults to today. include_planned_to_deliver decides whether quantities planned but not yet expected are swept in as well, and defaults to false, because a plan is not a receipt.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/mark_delivered" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"delivery_date": "2026-01-15",
"include_planned_to_deliver": false
}What it answers
json
{
"data": {
"delivery_date": "2026-09-18",
"delivery_line_items": [
{
"delivered_quantity": 240,
"expected_quantity": 240,
"variant_id": "44100920011"
},
{
"delivered_quantity": 180,
"expected_quantity": 200,
"variant_id": "44100920012"
}
],
"exceptions_opened": [],
"expected_delivery_date": null,
"id": "8841",
"movements": [
{
"id": 100241,
"quantity_delta": 240,
"variant_id": "44100920011"
},
{
"id": 100242,
"quantity_delta": 180,
"variant_id": "44100920012"
}
]
}
}What it refuses
- 400 A date could not be read.
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 No such purchase order, or no such delivery on it, for this organisation.
- 429
Create or amend an inbound at the warehouse
POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/push-to-wms
Scopes: purchase_orders:write
Creates or amends this order's inbound at the connected warehouse integration, which today means Helm WMS (helm-wms-direct). The first push creates the inbound purchase order; a pushed order is amended in place under the reference it was created with. The response carries one results entry per warehouse integration with what happened to it, the references the warehouse holds for this order, and the audit entry.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
purchase_order_id | path | The purchase order to push. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/push-to-wms" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"via": "<via>"
}What it answers
json
{
"data": {
"audit": {
"actor": "66c1f0a2e4b09a3d5c7f1a02",
"at": "2026-09-04T09:22:00+00:00",
"event": "pushed_to_wms",
"reason": "helm-wms-direct: created",
"via": "api"
},
"purchase_order_id": "9014",
"references": {
"helm-wms-direct": {
"id": "447102",
"reference": "PO-9014"
}
},
"results": [
{
"action": "created",
"integration": "helm-wms-direct"
}
]
}
}What it refuses
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 409 No warehouse connected, or the order is cancelled / on hold
- 429
Create many purchase orders in one call
POST /api/v1/organizations/{organization_id}/purchase-orders/bulk
Scopes: purchase_orders:write
Creates many purchase orders in one call, one per supplier and destination location, each with its own line items, and returns each created order with its id, number, line count and expected delivery date. This is the door an ERP pushes a night's worth of orders through, and the door that creates an order together with its lines.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/bulk" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"purchase_orders": [
{
"expected_delivery_date": "2026-01-15",
"line_items": [
{}
],
"location_id": "<location_id>",
"order_date": "2026-01-15"
}
]
}What it answers
json
{
"data": {
"purchase_orders": [
{
"expected_delivery_date": "2026-10-02",
"external_id": "PO-9014",
"id": "9014",
"location_id": "loc_0004",
"name": "PO-9014 Porto Knits",
"order_type": "purchase",
"source_location_id": null,
"supplier_id": "sup_0031",
"total_line_items": 12
}
],
"total_created": 1
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 A body field is missing or invalid; the refusal names the field.
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 429
List purchase order filters
GET /api/v1/organizations/{organization_id}/purchase-orders/filters
Scopes: purchase_orders:read
The values the purchase orders list can be filtered on: the statuses and order types this organisation's orders carry, and the suppliers and locations they name, each supplier and location as an id with its name. Read it to build filter_args for List purchase orders out of values that exist rather than values guessed from one page of rows.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/filters" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"location": [
{
"location_id": "loc_0004",
"location_name": "London DC"
}
],
"order_type": [
"purchase",
"manufacturing",
"transfer"
],
"status": [
"draft",
"approved",
"issued",
"delivered"
],
"supplier": [
{
"supplier_id": "sup_0031",
"supplier_name": "Porto Knits"
}
]
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 Bad Request - Invalid request parameters or validation errors
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot read Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 429
Create purchase orders from basket
POST /api/v1/organizations/{organization_id}/purchase-orders/from-basket
Scopes: purchase_orders:write
Turns the replenishment basket into purchase orders, one per supplier, and returns each created order with its supplier, its location and the Commitment it was stamped with at conversion (commitment_id null where its lane names none). The basket is cleared by the conversion.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/from-basket" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"export": false,
"filter_args": [
{
"key": "<key>",
"operation": "<operation>"
}
],
"limit": 1,
"offset": 1,
"search": "<search>",
"type": "variants"
}What it answers
json
{
"data": [
{
"commitment_id": "cmt_0091",
"commitment_reason": null,
"external_id": "PO-9017",
"id": "9017",
"location_id": "loc_0004",
"supplier_id": "sup_0031"
}
]
}What it refuses
- 400 Bad Request - Invalid request parameters or validation errors
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 Basket not found or empty
- 422 Unprocessable - one or more prospective purchase orders cannot be placed. Either a supplier's ordering constraint is unmet (minimum order value, minimum order quantity, batch size), or a line carries no unit cost, which leaves the supplier's minimum order value undecidable: an unknown cost can only add value, so an order below the minimum on its priced lines alone is not known to be below it. In that case the refusal names the unpriced LINES (
variant_unit_cost_missing) rather than reporting a minimum-order-value shortfall that could only be produced by valuing the unknown at zero. Nothing is created and the bench is left untouched. - 429
Update purchase order generation settings
PATCH /api/v1/organizations/{organization_id}/purchase-orders/generation-settings
Scopes: purchase_orders:write
Patches the standing rules the order generator follows: whether it drafts orders at all, how it groups lines into orders, how far ahead it looks, and whether fill_container lets it top up the last container of a proposal with whole variants the ranker names. A request that omits a flag leaves that flag as it was, and the response is the settings as they now stand.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X PATCH "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/generation-settings" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"enabled": false,
"exceptions": [
"<exceptions>"
],
"fill_container": false,
"filter_args": [
{
"key": "<key>",
"operation": "<operation>"
}
],
"round_to_batch_size": false,
"search": "<search>",
"top_up_to_moq": false,
"variant_ids": [
"<variant_ids>"
]
}What it answers
json
{
"data": {
"auto_generate": true,
"group_by": "supplier",
"horizon_days": 60
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 Bad Request - Invalid request parameters or validation errors
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 429
Create manufacturing orders in bulk
POST /api/v1/organizations/{organization_id}/purchase-orders/manufacturing/bulk
Scopes: purchase_orders:write
Creates manufacturing orders, which draw raw materials through the bill of materials rather than buying the finished item, and returns each with its id, number and line count.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A string of your own, up to 255 characters, that makes this write safe to retry. |
bash
curl -X POST "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/manufacturing/bulk" \
-H "Authorization: Bearer $TIGHTLY_API_KEY" \
-H "Content-Type: application/json" \
-d @body.jsonWhat to send, as body.json
json
{
"manufacturing_orders": [
{
"expected_delivery_date": "2026-01-15",
"location_id": "<location_id>",
"order_date": "2026-01-15",
"quantity": 1,
"recommended_location_id": "<recommended_location_id>",
"variant_id": "<variant_id>"
}
]
}What it answers
json
{
"data": {
"purchase_orders": [
{
"expected_delivery_date": "2026-10-16",
"external_id": "MO-9015",
"id": "9015",
"location_id": "loc_0004",
"name": "MO-9015 Terry Crew",
"order_type": "manufacturing",
"source_location_id": "loc_0004",
"supplier_id": null,
"total_line_items": 3
}
],
"total_created": 1
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 A body field is missing or invalid; the refusal names the field.
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot write Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 429
List purchase orders with pending supplier updates
GET /api/v1/organizations/{organization_id}/purchase-orders/with-supplier-updates
Scopes: purchase_orders:read
The pending supplier signals for one supplier: up to limit rows (default 3, maximum 100), newest source email first and orders with no date last, each naming the purchase order, its display name, the signal type and the date the supplier reported it. total_count counts every matching pending signal rather than the page.
| Required | In | What it is |
|---|---|---|
organization_id | path | The organisation. |
supplier_id | query | Supplier id to scope purchase orders and pending signals |
| Optional | In | What it is |
|---|---|---|
limit | query | Max number of supplier update rows to return (ordered newest first) |
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/with-supplier-updates?supplier_id=<supplier_id>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"items": [
{
"display_name": "PO-00009014",
"purchase_order_id": "9014",
"reported_at": "2026-09-02T08:11:00+00:00",
"signal_type": "reschedule"
}
],
"total_count": 4
},
"message": {
"desc": "",
"service": "purchase_order",
"severity": "INFO"
}
}What it refuses
- 400 Bad Request. Missing supplier_id, invalid limit, or limit out of range (1 to 100)
- 401
key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for. - 403
scope_missing: "This key cannot read Purchase orders." The key may not reach this operation.scope_missingwhen the key does not hold Purchase orders;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 429