Skip to content

Purchase orders ​

17 reads and 29 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
Read order amendment historyGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commercial-amendments
Propose a commercial order amendmentPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commercial-amendments
Apply the approved current amendmentPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commercial-amendments/{amendment_id}/apply
Approve or return an order amendmentPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commercial-amendments/{amendment_id}/decision
Review amendment funding and authorityGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commercial-amendments/{amendment_id}/review
What this order would draw from its CommitmentsGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commitment-draw
Read explicit purchase order customer allocationsGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/customer-allocations
Update explicit purchase order customer allocationsPUTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/customer-allocations
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
Read a supplier transmission outcome without resendingGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/issue/{request_id}
Prepare an immutable supplier PDF and email envelope for reviewPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/issue/review
Transmit a previously reviewed purchase-order PDF oncePOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/issue/reviewed
Preview historical customer attributionGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/lines/{line_id}/customer-attribution-review
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
Read reviewed purchase order reporting FXGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx
Record reviewed purchase order reporting FXPUTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx
Read historical receipt reporting evidenceGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx/receipt-baselines
Record reviewed historical receipt reporting evidencePUTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx/receipt-baselines
Read reviewed settlement reporting deltasGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx/settlement-baselines
Record reviewed settlement reporting deltasPUTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx/settlement-baselines
Read supplier responses and current agreement statusGETpurchase_orders:read/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/supplier-responses
Record a sourced supplier response to an order versionPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/supplier-responses
Capture the current commercial order versionPOSTpurchase_orders:write/api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/supplier-responses/revisions
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. Nothing is created.
  • 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
  • 409 Conflict - the order cannot be deleted. An order whose line ownership is recorded is refused; cancel it through update_purchase_order instead. Nothing was deleted.
  • 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

Patch status, dates, notes and lines by line-item id. On purchase orders, placed quantities, costs, purchase units, ship/cancel dates, addresses and agreement resolution require a reviewed revision and cannot change here. Draft edits, unchanged values, comments and operational arrival overrides remain available. Use dedicated reviewed timing, payment amendment or reduction paths where supported. Received quantities belong to Receipts; NetSuite commercial changes belong at source. Arrival changes require whole-company scope and PO write access. Attributed shared orders also require the current expected_arrival_revision from commitment-lines review; stale or incomplete review returns 409. Arrival changes expire outstanding supplier-source verification without changing accepted supplier/customer dates. Posted bills protect billed details. Retained placement evidence survives status resets.

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. Purchase orders also refuse unreviewed placed commercial amendments, receipt-counter edits, NetSuite-owned commercial edits and changes to posted-bill details. An order whose line ownership is recorded refuses added or removed lines and a changed commitment. 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

Read order amendment history ​

GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commercial-amendments

Scopes: purchase_orders:read

Requires an authenticated person and the appropriate Buy or Finance door. Proposals and decisions retain history without changing the order. Application rechecks the exact commercial version, sourced payment evidence, declared funding and authority on one transaction. Native USD amendments preserve received values and prior receipt bills; remaining quantities and prices require current per-line receipt revisions. Unsupported source-owned, frozen and held orders remain protected. This operation never sends a supplier message or executes a payment.

RequiredInWhat it is
organization_idpathOrganization that owns the purchase order.
purchase_order_idpathPurchase order whose amendment history and available actions are requested.
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>/commercial-amendments" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "purchase_order_id": "9014"
  }
}

What it refuses

  • 400 Invalid or unsupported amendment fields.
  • 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 Order or amendment not found in this organization.
  • 409 Evidence changed, funds are unavailable, or this identity has different content.
  • 429

Try it in the reference

Propose a commercial order amendment ​

POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commercial-amendments

Scopes: purchase_orders:write

Requires an authenticated person and the appropriate Buy or Finance door. Proposals and decisions retain history without changing the order. Application rechecks the exact commercial version, sourced payment evidence, declared funding and authority on one transaction. Native amendments preserve received values and prior receipt bills; Non-USD proposals bind the reviewed USD reporting quote with reporting_fx_request_id. Original and reporting amounts remain separate; remaining quantities and prices require current per-line receipt revisions. Unsupported source-owned, frozen and held orders remain protected. This operation never sends a supplier message or executes a payment.

RequiredInWhat it is
organization_idpathOrganization that owns the purchase order.
purchase_order_idpathPurchase order to review or amend.
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>/commercial-amendments" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "agreement_version_id": "<agreement_version_id>",
  "base_revision_id": "<base_revision_id>",
  "changes": [
    {
      "line_item_id": "<line_item_id>",
      "purchase_unit_cost": "<purchase_unit_cost>",
      "purchase_unit_definition_id": "<purchase_unit_definition_id>",
      "purchase_unit_revision": "<purchase_unit_revision>",
      "quantity": 1,
      "receipt_revision": "<receipt_revision>",
      "remaining_allocations": [
        {}
      ],
      "remaining_purchase_quantity": "<remaining_purchase_quantity>",
      "remaining_quantity": 1,
      "unit_cost": "<unit_cost>"
    }
  ],
  "reason": "<reason>",
  "reporting_fx_request_id": "<reporting_fx_request_id>",
  "request_id": "<request_id>"
}

What it answers

json
{
  "data": {
    "purchase_order_id": "9014"
  }
}

What it refuses

  • 400 Invalid or unsupported amendment fields.
  • 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 Order or amendment not found in this organization.
  • 409 Evidence changed, funds are unavailable, or this identity has different content.
  • 429

Try it in the reference

Apply the approved current amendment ​

POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commercial-amendments/{amendment_id}/apply

Scopes: purchase_orders:write

Requires an authenticated person and the appropriate Buy or Finance door. Proposals and decisions retain history without changing the order. Application rechecks the exact commercial version, sourced payment evidence, declared funding and authority on one transaction. Native USD amendments preserve received values and prior receipt bills; remaining quantities and prices require current per-line receipt revisions. Unsupported source-owned, frozen and held orders remain protected. This operation never sends a supplier message or executes a payment.

RequiredInWhat it is
organization_idpathOrganization that owns the purchase order.
purchase_order_idpathPurchase order to review or amend.
amendment_idpathImmutable amendment proposal returned when the change was submitted.
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>/commercial-amendments/<amendment_id>/apply" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "request_id": "<request_id>"
}

What it answers

json
{
  "data": {
    "purchase_order_id": "9014"
  }
}

What it refuses

  • 400 Invalid or unsupported amendment fields.
  • 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 Order or amendment not found in this organization.
  • 409 Evidence changed, funds are unavailable, or this identity has different content.
  • 429

Try it in the reference

Approve or return an order amendment ​

POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commercial-amendments/{amendment_id}/decision

Scopes: purchase_orders:write

Requires an authenticated person and the appropriate Buy or Finance door. Proposals and decisions retain history without changing the order. Application rechecks the exact commercial version, sourced payment evidence, declared funding and authority on one transaction. Native USD amendments preserve received values and prior receipt bills; remaining quantities and prices require current per-line receipt revisions. Unsupported source-owned, frozen and held orders remain protected. This operation never sends a supplier message or executes a payment.

RequiredInWhat it is
organization_idpathOrganization that owns the purchase order.
purchase_order_idpathPurchase order to review or amend.
amendment_idpathImmutable amendment proposal returned when the change was submitted.
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>/commercial-amendments/<amendment_id>/decision" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "decision": "approve",
  "note": "<note>",
  "request_id": "<request_id>",
  "review_fingerprint": "<review_fingerprint>"
}

What it answers

json
{
  "data": {
    "purchase_order_id": "9014"
  }
}

What it refuses

  • 400 Invalid or unsupported amendment fields.
  • 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 Order or amendment not found in this organization.
  • 409 Evidence changed, funds are unavailable, or this identity has different content.
  • 429

Try it in the reference

Review amendment funding and authority ​

GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/commercial-amendments/{amendment_id}/review

Scopes: purchase_orders:read

Requires an authenticated person and the appropriate Buy or Finance door. Proposals and decisions retain history without changing the order. Application rechecks the exact commercial version, sourced payment evidence, declared funding and authority on one transaction. Native USD amendments preserve received values and prior receipt bills; remaining quantities and prices require current per-line receipt revisions. Unsupported source-owned, frozen and held orders remain protected. This operation never sends a supplier message or executes a payment.

RequiredInWhat it is
organization_idpathOrganization that owns the purchase order.
purchase_order_idpathPurchase order to review or amend.
amendment_idpathImmutable amendment proposal returned when the change was submitted.
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>/commercial-amendments/<amendment_id>/review" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "purchase_order_id": "9014"
  }
}

What it refuses

  • 400 Invalid or unsupported amendment fields.
  • 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 Order or amendment not found in this organization.
  • 409 Evidence changed, funds are unavailable, or this identity has different content.
  • 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

Read explicit purchase order customer allocations ​

GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/customer-allocations

Scopes: purchase_orders:read

Explicit quantities allocated to channels and accounts. Remainders stay unallocated. Placement checks the approved commitment declaration and preserves its allocation evidence.

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>/customer-allocations" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "lines": [
      {
        "allocations": [
          {
            "account_id": "retailer-one",
            "channel_id": "wholesale",
            "quantity": 80
          }
        ],
        "frozen": null,
        "line_id": "21",
        "line_revision": {
          "currency": "USD",
          "quantity": 100,
          "unit_cost": "10.0"
        },
        "revision": 1
      }
    ],
    "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 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.
  • 409 Revision changed or order already placed.
  • 429

Try it in the reference

Update explicit purchase order customer allocations ​

PUT /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/customer-allocations

Scopes: purchase_orders:write

Explicit quantities allocated to channels and accounts. Remainders stay unallocated. Placement checks the approved commitment declaration and preserves its allocation evidence.

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 PUT "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/customer-allocations" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "allocations": [
    {
      "account_id": "<account_id>",
      "channel_id": "<channel_id>",
      "quantity": 1
    }
  ],
  "line_id": "<line_id>",
  "line_revision": {},
  "revision": 1
}

What it answers

json
{
  "data": {
    "lines": [
      {
        "allocations": [
          {
            "account_id": "retailer-one",
            "channel_id": "wholesale",
            "quantity": 80
          }
        ],
        "frozen": null,
        "line_id": "21",
        "line_revision": {
          "currency": "USD",
          "quantity": 100,
          "unit_cost": "10.0"
        },
        "revision": 1
      }
    ],
    "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 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.
  • 409 Revision changed or order already placed.
  • 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

Read a supplier transmission outcome without resending ​

GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/issue/{request_id}

Scopes: purchase_orders:read

Reads the current durable state for the same tenant, order and reviewing person. Use request_id latest to recover this person’s most recent receipt, or null when none exists. Attempting means transport may still be running or its outcome is unknown; uncertain means a failure occurred after the durable claim. Neither can retry or create another issue automatically. Accepted is provider call completion, never delivery or supplier acceptance. Prepared rows receive a refreshed document link.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order to issue to its supplier.
request_idpathThe durable request UUID, or latest for the same person’s most recently updated issue.
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>/issue/<request_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "body": "Purchase order PO9014 is attached.",
    "channel": "email",
    "created_at": "2026-09-25 12:00:00+00:00",
    "document_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "document_url": "https://files.example.com/purchase-order-9014.pdf",
    "provider_evidence": null,
    "purchase_order_id": "9014",
    "recipient": "supplier@example.com",
    "request_id": "dd935198-df8c-4106-b865-8d78d454e37c",
    "requested_by": "buyer-1",
    "retry_transport_allowed": true,
    "reviewed_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "sender": "buyer@example.com",
    "state": "prepared",
    "subject": "Purchase order PO9014",
    "supplier_acknowledged": false,
    "updated_at": "2026-09-25 12:00:00+00:00"
  }
}

What it refuses

  • 400 A reviewed request identity and hash are required.
  • 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 Missing tenant storage or another buyer has already attempted a supplier email.
  • 429

Try it in the reference

Prepare an immutable supplier PDF and email envelope for review ​

POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/issue/review

Scopes: purchase_orders:write

Creates a durable request with the current PDF bytes, document hash, resolved recipient and cover note. Sends nothing. A signed-in person must inspect this result before Transmit a previously reviewed purchase-order PDF once. Reusing the same UUID with different inputs is refused. A changed order needs a new UUID and review. The short-lived document link can be refreshed by Read a supplier transmission outcome without resending.

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/review" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "message": "<message>",
  "request_id": "<request_id>",
  "to_email": "<to_email>",
  "via": "<via>"
}

What it answers

json
{
  "data": {
    "body": "Purchase order PO9014 is attached.",
    "channel": "email",
    "created_at": "2026-09-25 12:00:00+00:00",
    "document_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "document_url": "https://files.example.com/purchase-order-9014.pdf",
    "provider_evidence": null,
    "purchase_order_id": "9014",
    "recipient": "supplier@example.com",
    "request_id": "dd935198-df8c-4106-b865-8d78d454e37c",
    "requested_by": "buyer-1",
    "retry_transport_allowed": true,
    "reviewed_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "sender": "buyer@example.com",
    "state": "prepared",
    "subject": "Purchase order PO9014",
    "supplier_acknowledged": false,
    "updated_at": "2026-09-25 12:00:00+00:00"
  }
}

What it refuses

  • 400 A reviewed request identity and hash are required.
  • 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 Stale review, changed PDF bytes, unresolved prior send, duplicate revision, on hold, cancelled or missing migration.
  • 429

Try it in the reference

Transmit a previously reviewed purchase-order PDF once ​

POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/issue/reviewed

Scopes: purchase_orders:write

Review first with Prepare an immutable supplier PDF and email envelope for review, inspect the recipient, cover note and document, then pass its request_id and reviewed_hash. The durable claim precedes email transport. Repeated calls read the same outcome and never resend. Accepted means the send call completed, not delivery or supplier acknowledgment. Attempting or uncertain outcomes prohibit transport retries and new issues for this order until independently reconciled. Calls without a reviewed identity now fail closed. API keys cannot use a personal mailbox.

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/reviewed" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "message": "<message>",
  "request_id": "<request_id>",
  "reviewed_hash": "<reviewed_hash>",
  "to_email": "<to_email>",
  "via": "<via>"
}

What it answers

json
{
  "data": {
    "channel": "email",
    "provider_evidence": {
      "delivery_confirmed": false,
      "message_id": null,
      "send_call_completed": true
    },
    "purchase_order_id": "9014",
    "request_id": "dd935198-df8c-4106-b865-8d78d454e37c",
    "retry_transport_allowed": false,
    "state": "accepted",
    "supplier_acknowledged": false
  }
}

What it refuses

  • 400 A reviewed request identity and hash are required.
  • 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 Stale review, changed PDF bytes, unresolved prior send, duplicate revision, on hold, cancelled or missing migration.
  • 429

Try it in the reference

Preview historical customer attribution ​

GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/lines/{line_id}/customer-attribution-review

Scopes: purchase_orders:read

Review present-day customer attribution of an existing placed order. This does not approve spending or recreate original approval. Existing original placement evidence is immutable. Requires unrestricted commitment scope; recording also requires Finance approver authority.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe existing purchase order to review.
line_idpathThe existing purchase-order line whose historical customer attribution is being reviewed.
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>/lines/<line_id>/customer-attribution-review" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "approved_channels": [],
    "commitment_id": "fw26",
    "declaration": {},
    "preview_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "review_declaration_version": 3,
    "reviewed_attribution": null,
    "source_revision": {
      "arrival": "2026-09-30",
      "commitment_id": "fw26",
      "line_id": 21,
      "line_revision": {
        "currency": "USD",
        "quantity": 100,
        "unit_cost": "10.0"
      },
      "line_source": {},
      "order_id": 9014,
      "source": {
        "netsuite_direct": {
          "id": "22550522"
        }
      },
      "variant_id": "1974"
    }
  }
}

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 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.
  • 409 Source or approval changed, ambiguous owner, missing migration, or attribution already fixed.
  • 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

Read reviewed purchase order reporting FX ​

GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx

Scopes: purchase_orders:read

Original amounts retain the existing currency registry exponent (0, 2, 3 or 4). Reporting is explicitly USD. Exact reviewed quotes never revalue captured receipts. A durable request UUID recovers original actor and submitted evidence. Finance approval access is required to write. This operation does not send payments or contact a provider.

RequiredInWhat it is
organization_idpathOrganization that owns this purchase order.
purchase_order_idpathPurchase order identity.
OptionalInWhat it is
request_idqueryOriginal committed request UUID for immutable recovery.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/reporting-fx" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "can_record": true,
    "current": null,
    "expected_revision": 0,
    "exponent": 2,
    "order_currency": "EUR",
    "purchase_order_id": "1",
    "reporting_currency": "USD",
    "rounding": "half_up"
  }
}

What it refuses

  • 400 Invalid exact amount, currency, date or reviewed evidence.
  • 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 Order or original request does not exist.
  • 409 Quote, receipt source or expected revision changed, or request identity differs.
  • 429

Try it in the reference

Record reviewed purchase order reporting FX ​

PUT /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx

Scopes: purchase_orders:write

Original amounts retain the existing currency registry exponent (0, 2, 3 or 4). Reporting is explicitly USD. Exact reviewed quotes never revalue captured receipts. A durable request UUID recovers original actor and submitted evidence. Finance approval access is required to write. This operation does not send payments or contact a provider.

RequiredInWhat it is
organization_idpathOrganization that owns this purchase order.
purchase_order_idpathPurchase order identity.
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 PUT "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/reporting-fx" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "expected_revision": 1,
  "original_currency": "USD",
  "quoted_on": "2026-01-15",
  "rate": "<rate>",
  "reporting_currency": "USD",
  "request_id": "<request_id>",
  "review_evidence": "<review_evidence>",
  "source": "<source>"
}

What it answers

json
{
  "data": {
    "actor": "6abbd9a72d8779cd4a90a23b",
    "created_at": "2026-09-29T15:30:48.462116+00:00",
    "input_hash": "3b11dbea40f37e7f380010d0376d5e9d7539c7c96b10be02f77b6a5dee55aee8",
    "original_currency": "EUR",
    "purchase_order_id": 1,
    "quoted_on": "2026-09-29",
    "rate": "1.080000000000000000",
    "reporting_currency": "USD",
    "request_id": "4f526f85-15f4-415c-b4f4-0e0ea8bd7f8c",
    "review_evidence": "Finance reviewed the remaining purchase reporting rate",
    "revision": 1,
    "rounding": "half_up",
    "source": "Reviewed treasury quote",
    "submitted": {
      "expected_revision": 0,
      "original_currency": "EUR",
      "quoted_on": "2026-09-29",
      "rate": "1.08",
      "reporting_currency": "USD",
      "request_id": "4f526f85-15f4-415c-b4f4-0e0ea8bd7f8c",
      "review_evidence": "Finance reviewed the remaining purchase reporting rate",
      "source": "Reviewed treasury quote"
    }
  }
}

What it refuses

  • 400 Invalid exact amount, currency, date or reviewed evidence.
  • 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 Order or original request does not exist.
  • 409 Quote, receipt source or expected revision changed, or request identity differs.
  • 429

Try it in the reference

Read historical receipt reporting evidence ​

GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx/receipt-baselines

Scopes: purchase_orders:read

Original amounts retain the existing currency registry exponent (0, 2, 3 or 4). Reporting is explicitly USD. Exact reviewed quotes never revalue captured receipts. A durable request UUID recovers original actor and submitted evidence. Finance approval access is required to write. This operation does not send payments or contact a provider.

RequiredInWhat it is
organization_idpathOrganization that owns this purchase order.
purchase_order_idpathPurchase order identity.
OptionalInWhat it is
request_idqueryOriginal committed request UUID for immutable recovery.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/reporting-fx/receipt-baselines" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "can_record": true,
    "expected_revision": 0,
    "purchase_order_id": "1",
    "receipts": [
      {
        "delivery_line_id": 1,
        "line_id": 1,
        "needs_review": true,
        "original_amount_minor": 4000,
        "original_currency": "EUR",
        "original_exponent": 2,
        "quantity": 4,
        "received_on": "2026-09-29",
        "reporting_authority": null,
        "source_fingerprint": "0a16c287dbd2b31ac8409c8266da444b5cb8d0fd1f0b621b123b4901bc3acf72"
      }
    ],
    "reporting_currency": "USD",
    "rounding": "half_up"
  }
}

What it refuses

  • 400 Invalid exact amount, currency, date or reviewed evidence.
  • 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 Order or original request does not exist.
  • 409 Quote, receipt source or expected revision changed, or request identity differs.
  • 429

Try it in the reference

Record reviewed historical receipt reporting evidence ​

PUT /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx/receipt-baselines

Scopes: purchase_orders:write

Original amounts retain the existing currency registry exponent (0, 2, 3 or 4). Reporting is explicitly USD. Exact reviewed quotes never revalue captured receipts. A durable request UUID recovers original actor and submitted evidence. Finance approval access is required to write. This operation does not send payments or contact a provider.

RequiredInWhat it is
organization_idpathOrganization that owns this purchase order.
purchase_order_idpathPurchase order identity.
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 PUT "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/reporting-fx/receipt-baselines" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "expected_revision": 1,
  "receipts": [
    {
      "delivery_line_id": 1,
      "quoted_on": "2026-01-15",
      "rate": "<rate>",
      "review_evidence": "<review_evidence>",
      "source": "<source>",
      "source_fingerprint": "<source_fingerprint>"
    }
  ],
  "request_id": "<request_id>"
}

What it answers

json
{
  "data": {
    "actor": "6abbd9a72d8779cd4a90a23b",
    "purchase_order_id": "1",
    "receipts": [
      {
        "actor": "6abbd9a72d8779cd4a90a23b",
        "created_at": "2026-09-29T15:30:48.477662+00:00",
        "delivery_line_id": 1,
        "line_id": 1,
        "original_amount_minor": 4000,
        "original_currency": "EUR",
        "original_exponent": 2,
        "quantity": 4,
        "quoted_on": "2026-09-29",
        "rate": "1.100000000000000000",
        "reporting_amount_minor": 4400,
        "reporting_currency": "USD",
        "request_id": "311b714b-33dc-408f-9889-bad90d8832ac",
        "review_evidence": "Reviewed the reporting rate applicable to this received quantity",
        "source": "Historical receipt treasury quote",
        "source_fingerprint": "0a16c287dbd2b31ac8409c8266da444b5cb8d0fd1f0b621b123b4901bc3acf72"
      }
    ],
    "request_id": "311b714b-33dc-408f-9889-bad90d8832ac",
    "revision": 1,
    "rounding": "half_up",
    "submitted": {
      "expected_revision": 0,
      "receipts": [
        {
          "delivery_line_id": 1,
          "quoted_on": "2026-09-29",
          "rate": "1.10",
          "review_evidence": "Reviewed the reporting rate applicable to this received quantity",
          "source": "Historical receipt treasury quote",
          "source_fingerprint": "0a16c287dbd2b31ac8409c8266da444b5cb8d0fd1f0b621b123b4901bc3acf72"
        }
      ],
      "request_id": "311b714b-33dc-408f-9889-bad90d8832ac"
      …
    }
  }
}

What it refuses

  • 400 Invalid exact amount, currency, date or reviewed evidence.
  • 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 Order or original request does not exist.
  • 409 Quote, receipt source or expected revision changed, or request identity differs.
  • 429

Try it in the reference

Read reviewed settlement reporting deltas ​

GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx/settlement-baselines

Scopes: purchase_orders:read

Preserves prior reporting paid and credit values. Each positive newly reconciled delta requires a separate reviewed quote. Zero deltas retain prior evidence. Reduced balances require explicit reversal evidence. Original request recovery is available by request_id.

RequiredInWhat it is
organization_idpathOrganization that owns this purchase order.
purchase_order_idpathPurchase order identity.
OptionalInWhat it is
request_idqueryOriginal committed request UUID for immutable recovery.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/reporting-fx/settlement-baselines" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "balances": [
      {
        "milestone_id": "net",
        "needs_review": true,
        "observed_on": "2026-09-29",
        "original_credit_amount_minor": 500,
        "original_currency": "EUR",
        "original_exponent": 2,
        "original_paid_amount_minor": 2000,
        "previous_settlement_request_id": null,
        "prior_credit_amount_minor": 0,
        "prior_paid_amount_minor": 0,
        "prior_reporting_credit_amount_minor": 0,
        "prior_reporting_paid_amount_minor": 0,
        "reporting_authority": null,
        "settlement_request_id": "9eeec2af-eb14-4809-8eb9-ea0e09a2adbd",
        "source_fingerprint": "1e8678ef9e7b208d04e944f805ae8e3b941ee44d1121c60e998b296c16a53dea",
        "unvalued_credit_amount_minor": 500,
        "unvalued_paid_amount_minor": 2000
      }
    ],
    "can_record": true,
    "expected_revision": 0,
    "purchase_order_id": "1",
    "reporting_currency": "USD",
    "rounding": "half_up"
  }
}

What it refuses

  • 400 Invalid exact amount, currency, date or reviewed evidence.
  • 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 Order or original request does not exist.
  • 409 Quote, receipt source or expected revision changed, or request identity differs.
  • 429

Try it in the reference

Record reviewed settlement reporting deltas ​

PUT /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/reporting-fx/settlement-baselines

Scopes: purchase_orders:write

Preserves original reporting paid and credit values. Positive deltas require reviewed quotes. Negative deltas require explicit allocations to original settlement captures, retaining their frozen reporting values and residual rounding; no new quote is accepted. Optional confirmed bill credit or void evidence binds the original request and event sequence. The durable request UUID recovers the original actor and complete submitted evidence.

RequiredInWhat it is
organization_idpathOrganization that owns this purchase order.
purchase_order_idpathPurchase order identity.
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 PUT "https://api.app.tightly.io/api/v1/organizations/<organization_id>/purchase-orders/<purchase_order_id>/reporting-fx/settlement-baselines" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "balances": [
    {
      "credit_quote": {
        "quoted_on": "<quoted_on>",
        "rate": "<rate>",
        "review_evidence": "<review_evidence>",
        "source": "<source>"
      },
      "credit_reversal": {
        "allocations": [],
        "bill_evidence": {},
        "review_evidence": "<review_evidence>",
        "source": "<source>"
      },
      "milestone_id": "<milestone_id>",
      "paid_quote": {
        "quoted_on": "<quoted_on>",
        "rate": "<rate>",
        "review_evidence": "<review_evidence>",
        "source": "<source>"
      },
      "paid_reversal": {
        "allocations": [],
        "bill_evidence": {},
        "review_evidence": "<review_evidence>",
        "source": "<source>"
      },
      "settlement_request_id": "<settlement_request_id>",
      "source_fingerprint": "<source_fingerprint>"
    }
  ],
  "expected_revision": 1,
  "request_id": "<request_id>"
}

What it answers

json
{
  "data": {
    "actor": "6abbdd23ac00701d4bc92982",
    "balances": [
      {
        "actor": "6abbdd23ac00701d4bc92982",
        "created_at": "2026-09-29T15:45:40.270326+00:00",
        "credit_quote": {
          "quoted_on": "2026-09-29",
          "rate": "1.08",
          "review_evidence": "Reviewed reporting value of this new settlement delta",
          "source": "Reviewed bank statement FX"
        },
        "milestone_id": "net",
        "original_credit_amount_minor": 500,
        "original_currency": "EUR",
        "original_exponent": 2,
        "original_paid_amount_minor": 2000,
        "paid_quote": {
          "quoted_on": "2026-09-29",
          "rate": "1.10",
          "review_evidence": "Reviewed reporting value of this new settlement delta",
          "source": "Reviewed bank statement FX"
        },
        "previous_settlement_request_id": null,
        "prior_credit_amount_minor": 0,
        "prior_paid_amount_minor": 0,
        "prior_reporting_credit_amount_minor": 0,
        "prior_reporting_paid_amount_minor": 0,
        "purchase_order_id": 1,
        "reporting_credit_amount_minor": 540,
        "reporting_currency": "USD",
        "reporting_paid_amount_minor": 2200,
        "request_id": "fd5d394d-96d9-4839-ac66-21479664ac9b",
        "settlement_request_id": "9eeec2af-eb14-4809-8eb9-ea0e09a2adbd",
        "source_fingerprint": "1e8678ef9e7b208d04e944f805ae8e3b941ee44d1121c60e998b296c16a53dea"
      }
    ],
    "purchase_order_id": "1",
    "request_id": "fd5d394d-96d9-4839-ac66-21479664ac9b"
    …
  }
}

What it refuses

  • 400 Invalid exact amount, currency, date or reviewed evidence.
  • 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 Order or original request does not exist.
  • 409 Quote, receipt source or expected revision changed, or request identity differs.
  • 429

Try it in the reference

Read supplier responses and current agreement status ​

GET /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/supplier-responses

Scopes: purchase_orders:read

Returns captured commercial versions and sourced line answers. Provider transmission never counts as supplier acceptance. Only all positive-quantity lines explicitly accepted for the current commercial version yield accepted; proposed changes, rejection, partial, older and unknown-version evidence remain distinct.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order whose supplier response is being recorded or read.
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>/supplier-responses" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "basis": "latest_sourced_answers_to_current_commercial_revision",
    "counts": {
      "accepted": 0,
      "answered_current": 0,
      "rejected": 0,
      "required": 1
    },
    "current_revision_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "current_snapshot": {
      "header": {
        "payment_terms_snapshot": null,
        "supplier_id": "supplier-1"
      },
      "lines": [
        {
          "currency": "USD",
          "line_item_id": "100",
          "purchase_units": null,
          "quantity": 10,
          "unit_cost": "12",
          "variant_id": "SKU-1"
        }
      ],
      "purchase_order_id": "9014",
      "schema_version": 1
    },
    "operational_context": {
      "expected_delivery_date": null
    },
    "purchase_order_id": "9014",
    "responses": [],
    "revisions": [],
    "state": "awaiting_response"
  }
}

What it refuses

  • 400 A reviewed request identity and hash are required.
  • 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 Order or captured version not found in this organization.
  • 409 Order changed concurrently, or request identity was already used with different input.
  • 429

Try it in the reference

Record a sourced supplier response to an order version ​

POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/supplier-responses

Scopes: purchase_orders:write

Requires a signed-in person with write access. Records source, response time and line answers against a server-captured version, or explicitly unknown version. Older and unknown responses do not update current quantities. Exact-current responses update confirmed quantities only; ordered terms, financial approval and transmission are unchanged. An identical UUID replay returns its receipt; changed input conflicts. Receipt, applicable line changes and audit commit together.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order whose supplier response is being recorded or read.
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>/supplier-responses" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "answered_at": "2026-01-15T09:00:00Z",
  "lines": [
    {
      "confirmed_quantity": 1,
      "line_item_id": 1,
      "reason": "<reason>",
      "variant_id": "<variant_id>",
      "verdict": "accepted"
    }
  ],
  "request_id": "<request_id>",
  "revision_id": "<revision_id>",
  "source_reference": "<source_reference>",
  "source_type": "email"
}

What it answers

json
{
  "data": {
    "answered_at": "2026-09-20T09:00:00+00:00",
    "applied": false,
    "lines": [
      {
        "confirmed_quantity": 10,
        "line_item_id": "100",
        "ordered_quantity": 10,
        "reason": null,
        "variant_id": "SKU-1",
        "verdict": "accepted"
      }
    ],
    "purchase_order_id": "9014",
    "recorded_at": "2026-09-20T10:00:00+00:00",
    "recorded_by": "buyer",
    "recorded_revision_state": "unknown",
    "request_id": "11111111-1111-4111-8111-111111111111",
    "revision_id": null,
    "revision_state": "unknown",
    "source_reference": "Supplier message 123",
    "source_type": "email"
  }
}

What it refuses

  • 400 A reviewed request identity and hash are required.
  • 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 Order or captured version not found in this organization.
  • 409 Order changed concurrently, or request identity was already used with different input.
  • 429

Try it in the reference

Capture the current commercial order version ​

POST /api/v1/organizations/{organization_id}/purchase-orders/{purchase_order_id}/supplier-responses/revisions

Scopes: purchase_orders:write

Requires a signed-in person with write access. Stores a server-owned snapshot of order terms; it does not record supplier agreement. Repeated capture of identical content returns the existing version. The empty body cannot supply a historical hash or snapshot.

RequiredInWhat it is
organization_idpathThe organisation.
purchase_order_idpathThe purchase order whose supplier response is being recorded or read.
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>/supplier-responses/revisions" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What it answers

json
{
  "data": {
    "captured_at": "2026-09-20T10:00:00+00:00",
    "captured_by": "buyer",
    "revision_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "snapshot": {
      "header": {
        "payment_terms_snapshot": null,
        "supplier_id": "supplier-1"
      },
      "lines": [
        {
          "currency": "USD",
          "line_item_id": "100",
          "purchase_units": null,
          "quantity": 10,
          "unit_cost": "12",
          "variant_id": "SKU-1"
        }
      ],
      "purchase_order_id": "9014",
      "schema_version": 1
    }
  }
}

What it refuses

  • 400 A reviewed request identity and hash are required.
  • 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 Order or captured version not found in this organization.
  • 409 Order changed concurrently, or request identity was already used with different input.
  • 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 and the refusal names the field. Also returned when a TRANSFER entry draws more of a variant than its source location holds, summed across the request. 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

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
{
  "expected_delivery_date": "2026-01-15",
  "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.