Skip to content

Purchase orders ​

8 reads and 18 writes, on train 2026-11. Scopes: purchase_orders:read · purchase_orders:write.

OperationMethodScopePath
List purchase ordersGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders
Open a purchase orderPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders
Delete Purchase OrderDELETEpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}
Get purchase orderGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}
Update purchase orderPATCHpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}
Approve a purchase orderPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/approve
Reserve the raw materials a manufacturing order needsPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/auto-allocate
What this order would draw from its CommitmentsGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commitment-draw
Get purchase order deliveriesGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries
Create purchase order deliveryPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries
Remove a delivery recorded in errorDELETEpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/{delivery_id}
One recorded delivery against a purchase orderGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/{delivery_id}
Correct a recorded deliveryPATCHpurchase_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 callPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/deliveries/bulk
Duplicate purchase orderPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/duplicate
Export purchase orderGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/export
Stop an order and say whyPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/hold
Send the purchase order to its supplier by their channelPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/issue
Close an order out by recording everything outstanding as deliveredPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/mark_delivered
Create or amend an inbound at the warehousePOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/push-to-wms
Create many purchase orders in one callPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/bulk
List purchase order filtersGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/filters
Create purchase orders from basketPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/from-basket
Update purchase order generation settingsPATCHpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/generation-settings
Create manufacturing orders in bulkPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/manufacturing/bulk
List purchase orders with pending supplier updatesGETpurchase_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.

RequiredInWhat it is
organization_idpathThe organisation.
OptionalInWhat it is
limitqueryPagination limit (defaults to 10 when neither limit nor offset is provided)
offsetqueryPagination offset
filter_argsqueryJSON array of filter conditions.
sort_argsqueryComma-separated list of sort arguments.
searchquerySearch term to filter purchase orders
Tightly-VersionheaderThe 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathUnique identifier for the purchase order to delete
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 404 Purchase order not found
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathUnique identifier for the purchase order
OptionalInWhat it is
sort_argsqueryComma-separated list of sort arguments for line items.
Tightly-VersionheaderThe 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 404 Purchase order not found
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathUnique identifier for the purchase order to update
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when 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

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order to approve.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 409 Already confirmed, on hold, or refused by the OTB guardrail / commitment envelope
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when 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

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order.
OptionalInWhat it is
Tightly-VersionheaderThe 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_excludes when 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when 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

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order ID
OptionalInWhat it is
Tightly-VersionheaderThe 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 404 Purchase order not found
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order ID
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 404 Purchase order not found
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order.
delivery_idpathThe delivery on that order.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when 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_bill when the receipt has already been billed to Xero or QuickBooks. Void the bill in the ledger before changing or removing the receipt.
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order.
delivery_idpathThe delivery on that order.
OptionalInWhat it is
Tightly-VersionheaderThe 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when 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

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order.
delivery_idpathThe delivery on that order.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when 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_bill when the receipt has already been billed to Xero or QuickBooks. Void the bill in the ledger before changing or removing the receipt.
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when 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

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathUnique identifier for the purchase order to duplicate
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 404 Purchase order not found
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathUnique identifier for the purchase order to export
formatqueryThe file the export is rendered as. Defaults to PDF.
OptionalInWhat it is
fieldsqueryComma-separated columns to export.
Tightly-VersionheaderThe 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 404 Purchase order not found
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order to put on hold.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 409 Already on hold, or not on hold when releasing
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order to issue to its supplier.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 409 Wrong order type, cancelled, on hold, or no recipient resolvable
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when 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

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order to push.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 409 No warehouse connected, or the order is cancelled / on hold
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
OptionalInWhat it is
Tightly-VersionheaderThe 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when 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

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA 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.json

What 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 429

Try it in the reference

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.

RequiredInWhat it is
organization_idpathThe organisation.
supplier_idquerySupplier id to scope purchase orders and pending signals
OptionalInWhat it is
limitqueryMax number of supplier update rows to return (ordered newest first)
Tightly-VersionheaderThe 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_missing when the key does not hold Purchase orders; organization_mismatch when the path names an organisation that is not the key's; ip_not_allowed when the caller's address is outside the key's allowlist.
  • 429

Try it in the reference

Tightly API, version 2026-11.