Skip to main content

ConsignTrak JSON API (1.0.0-dev)

Download OpenAPI specification:Download

JSON API for ConsignTrak — consignment warehouse management system. Authenticated via API keys (Bearer token). All responses use JSON.

This spec documents only the /api/v1/ endpoints. Server-rendered HTML endpoints (the screens people use) are not part of the API.

Items

Item master — inventory items with fuzzy search

List items

Paginated list of items. Supply-partner-scoped keys only see their supply partner's items. Filter / status / category accept repeated values (?filter=A&filter=B); single-value calls keep working as one-element arrays. Search hits item number, description, and alias normalized values (#1021).

Authorizations:
BearerAuth
query Parameters
manufacturer
string

Filter by supply partner ID (ignored for supply-partner-scoped keys)

status
Array of strings
Items Enum: "active" "discontinued" "inactive"

One or more item statuses. Defaults to active when omitted. Repeat the param to combine: ?status=active&status=discontinued.

filter
Array of strings
Items Enum: "negative" "oversold" "out_of_stock" "below_reorder" "missing_price"

Stock anomaly filters. Multiple values OR together (?filter=out_of_stock&filter=below_reorder returns items matching either bucket).

category
Array of strings

Filter by items.category. Multiple values OR together.

location
Array of strings

Filter by items.warehouse_location. Multiple values OR together. (#1021 Phase 2)

uom
Array of strings

Filter by items.selling_uom. Multiple values OR together. (#1021 Phase 2)

movement
string
Enum: "30d" "90d" "180d" "365d"

Restricts to items with no movement in at least N days (including items that have never moved — date_last_movement IS NULL). Unknown tokens are ignored. (#1021 Phase 2)

velocity
string
Enum: "fast" "slow" "dead"

Velocity bucket over a 365-day window of outbound shipments: fast = top 25%, slow = bottom 25% (non-zero), dead = no shipments. Unknown tokens are ignored. (#1021 Phase 2)

sort
string
Enum: "mfg_code" "item_number" "description" "on_hand_asc" "on_hand_desc" "available_asc" "last_movement_desc"

Sort order. Unknown values fall back to mfg_code.

q
string

Search query (item number, description, or item alias normalized).

page
integer
Default: 1
per_page
integer <= 100
Default: 50

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Create item (Phase 3a)

Creates a new item. Requires inventory.edit. Supply-partner-scoped keys can only create items for their own supply partner (403 otherwise).

Returns 409 duplicate_item_number when an item with the same manufacturer + display number already exists.

Authorizations:
BearerAuth
Request Body schema: application/json
required
manufacturer_id
required
string <uuid>
item_number_display
required
string
description
required
string
description_extended
string
category
string
selling_uom
string
Default: "EA"
purchase_uom
string
warehouse_location
string
unit_weight
number

Responses

Request samples

Content type
application/json
{
  • "manufacturer_id": "cafecd41-5060-405f-a23d-b38c40f09e3f",
  • "item_number_display": "string",
  • "description": "string",
  • "description_extended": "string",
  • "category": "string",
  • "selling_uom": "EA",
  • "purchase_uom": "string",
  • "warehouse_location": "string",
  • "unit_weight": 0
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Search items by part number

Fuzzy search via pg_trgm. Returns up to 20 matches. Match strategy is two-level: exact normalized match first, then prefix/substring, then trigram fuzzy. Result keys are PascalCase (the Go struct has no json tags) — see the ItemSearchResult schema.

Authorizations:
BearerAuth
query Parameters
q
required
string
manufacturer
string

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Get item detail

Returns 404 for items outside the key's supply partner scope.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update item (Phase 3a)

Updates editable item fields. Requires inventory.edit. Supply-partner-scoped keys can only update their own supply partner's items (404 cross-mfg).

PATCH semantics: omitted fields preserve the existing value; present fields write the supplied value. Empty strings clear NULLable fields (matches the web form's NULLIF handling). item_number_display and manufacturer_id are immutable.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
description
string
description_extended
string
category
string
selling_uom
string
purchase_uom
string
warehouse_location
string
unit_weight
number
reorder_level
integer >= 0

Issue #953. Omitted preserves the existing value; present writes it. Negative values are rejected with 400. The same field is editable by supply partners through the consignor portal at POST /portal/inventory/{id}/field when their org has portal_inventory_edit_enabled = true.

Responses

Request samples

Content type
application/json
{
  • "description": "string",
  • "description_extended": "string",
  • "category": "string",
  • "selling_uom": "string",
  • "purchase_uom": "string",
  • "warehouse_location": "string",
  • "unit_weight": 0,
  • "reorder_level": 0
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Discontinue item (Phase 3a)

Marks an item as discontinued. Optionally records a successor — the successor must belong to the same manufacturer, must be active, and cannot be the item itself.

When the discontinued item still has stock on hand, the response includes a structured stock_warning object (the web equivalent surfaces this as a ?warning= query string on the post-redirect URL).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
optional
superseded_by
string <uuid>

Optional successor item id

reason
string

Responses

Request samples

Content type
application/json
{
  • "superseded_by": "a99fa58d-1b9b-4688-ad5f-382f6b17ef46",
  • "reason": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Reactivate discontinued item (Phase 3a)

Returns the active item. Requires inventory.edit.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
optional
reason
string

Responses

Request samples

Content type
application/json
{
  • "reason": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List the bins holding an item's stock (#1396)

Bin-level stock for one item. Requires inventory.view. Supply-partner- scoped actors get 404 for items belonging to another supply partner (existence is not leaked).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Orders

Order entry with atomic creation (header + lines)

List ship-via carriers with mode

Read-only reference list of active ship-via carriers (#1411). Use the id as ship_via_id when creating or patching orders, and mode (parcel / ltl / will_call) to classify orders the same way the warehouse pick / pack / ship pill does. Carriers are global (not supply-partner scoped). Ordered UPS, FedEx, LTL, Will Call, then any other carriers alphabetically.

Requires orders.view permission.

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

List orders

Paginated list of orders. Supply-partner-scoped keys only see their supply partner's orders.

Each order in data carries a display_substatus field (issue #1067) when the order has an active pick batch, whatever the order's status. Values: "picking" (a batch is released or in progress), "picked" (the least-progressed active batch is completed), or "packed" (every active batch is packed). The field is omitted for orders with no active batch.

Triage filters (#1416) mirror the office orders list: q is a case-insensitive substring match across order number, customer PO, customer name, release number, and original release number — the way to find "order 130664" or "PO 244875C03" from a support report. status and priority accept repeated params or comma lists; status=open expands to entered,released,partial. Unknown status/priority values and non-positive aged_hours are a 400, never silently dropped.

Authorizations:
BearerAuth
query Parameters
manufacturer
string
customer
string
status
Array of strings
Items Enum: "open" "entered" "selected" "released" "partial" "shipped" "billed" "closed" "cancelled"

Repeatable / comma-separated. One of open (macro), entered, selected, released, partial, shipped, billed, closed, cancelled. Combined with awaiting_direct_ship by OR (union), matching the UI's status pills.

q
string

Substring search over order number, customer PO, customer name, release number, original release number (#1416).

priority
Array of strings
Items Enum: "normal" "high" "rush"

Repeatable / comma-separated; normal, high, rush (#1416).

aged_hours
integer >= 1

Only orders created at least this many hours ago (#1416). Must be a positive integer.

awaiting_direct_ship
string
Value: "1"

1 restricts to released/partial orders with a direct-ship line not yet shipped (#1416). OR-combined with status.

from
string <date>
to
string <date>
page
integer
Default: 1
per_page
integer <= 100
Default: 25

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Create order atomically (header + lines)

Creates an order with all lines in a single transaction. Requires both orders.create and orders.edit permissions. If any line fails validation, the entire order is rolled back.

Authorizations:
BearerAuth
Request Body schema: application/json
required
manufacturer_id
required
string <uuid>
customer_id
required
string <uuid>
order_date
string <date>

YYYY-MM-DD (defaults to today)

customer_po_number
string
manufacturer_auth
string
freight_terms
string

Freight terms (e.g. COLLECT, PREPAID, 3RD PARTY, WILL CALL). Selecting the Will Call carrier in ship_via_id forces this to WILL CALL regardless of the value sent — the customer-pickup workflow keys off freight_terms (#1369).

ship_via_id
string or null <uuid>

Carrier — a ship_via_codes UUID (UPS / FedEx / LTL / Will Call seeded in every environment). Anchors the ship-via triple below. The Will Call carrier is the customer-pickup signal: setting it forces freight_terms = "WILL CALL" (#1369).

ship_method
string or null

Parcel service level (e.g. "Ground", "Next Day Air"). Normalized against the carrier on write: kept for parcel carriers (UPS/FedEx), cleared for LTL / Will Call.

ship_via_other
string or null

Free-text LTL carrier write-in (e.g. "Old Dominion"). Kept only when the carrier is LTL; cleared otherwise.

carrier_account
string
shipping_point
string
special_instructions
string
priority
string
Default: "normal"
Enum: "normal" "high" "rush"

Workflow priority. High/rush values sort the pick queue ahead of normal, surface the order in the dashboard attention list, and render a pill on pick/pack queues. Applied via SetOrderPriority after create — the same audit event fires as for the HTML form path.

drop_ship_location_id
string or null <uuid>

When set, the order ships from the warehouse to a third-party address (the customer's customer, a job site, etc.). The location must belong to customer_id and be active. Its address is snapshotted into the order's ship_to_* fields at create time; later edits to the location row do not affect this order. Distinct from per-line fulfillment_source = direct, which is a supply-partner-ships-from-their-warehouse swap.

third_party_payer_id
string or null <uuid>

Applied only when freight_terms = "3RD PARTY". When set, the payer's address + carrier account are snapshotted into the order's bill_to_* and carrier_account. The payer must belong to customer_id and be active, else the create is rejected.

bill_to_name
string

Inline 3rd-party bill-to (used when third_party_payer_id is absent and freight_terms = "3RD PARTY").

bill_to_address_1
string
bill_to_address_2
string
bill_to_city
string
bill_to_state
string
bill_to_zip
string
required
Array of objects (CreateOrderLineRequest) non-empty

Responses

Request samples

Content type
application/json
{
  • "manufacturer_id": "cafecd41-5060-405f-a23d-b38c40f09e3f",
  • "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
  • "order_date": "2019-08-24",
  • "customer_po_number": "string",
  • "manufacturer_auth": "string",
  • "freight_terms": "string",
  • "ship_via_id": "6a32d3b2-64b8-40df-becf-ad404313c722",
  • "ship_method": "string",
  • "ship_via_other": "string",
  • "carrier_account": "string",
  • "shipping_point": "string",
  • "special_instructions": "string",
  • "priority": "normal",
  • "drop_ship_location_id": "5513359a-f70b-460c-8b99-95db86e236bf",
  • "third_party_payer_id": "22660371-5b32-496f-88ee-ca4422325933",
  • "bill_to_name": "string",
  • "bill_to_address_1": "string",
  • "bill_to_address_2": "string",
  • "bill_to_city": "string",
  • "bill_to_state": "string",
  • "bill_to_zip": "string",
  • "lines": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get order detail with lines (+ optional triage context)

Default payload is {order, lines} and is unchanged. include (#1416, repeatable or comma-separated) adds the context the HTML order page shows, one top-level key per value:

  • fulfillment — display sub-status, warehouse/direct split, pending direct-ship lines, and the lifecycle timeline. lifecycle.batch_id is the hook into GET /api/v1/pick-batches/{batchID}.
  • shipments — every pick batch with tracking + parcels (same shape as GET /api/v1/orders/{orderID}/pick-batches).
  • blocks — open order blocks, each with a summary of the exception behind it; the full row is GET /api/v1/exceptions/....
  • documents — order attachments plus the current packing list.

An unknown include value is a 400. Supply-partner-scoped actors get 404 for another supply partner's order before any include runs.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
query Parameters
include
Array of strings
Items Enum: "fulfillment" "shipments" "blocks" "documents"

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update order header (Phase 3e)

PATCH semantics — omitted fields preserve. Empty strings are normalized to nil so the service's COALESCE($N, existing) preserves the column; explicit non-empty strings overwrite. The priority field is optional and applied via SetOrderPriority (separate audit row when it changes). Returns the reloaded order + lines.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
customer_po_number
string or null
manufacturer_auth
string or null
freight_terms
string or null

Setting ship_via_id to the Will Call carrier forces this to "WILL CALL" (#1369).

carrier_account
string or null
shipping_point
string or null
special_instructions
string or null
priority
string
Enum: "normal" "high" "rush"
ship_via_id
string or null <uuid>
ship_method
string or null
ship_via_other
string or null
third_party_payer_id
string or null <uuid>

Snapshots the payer into bill_to_* + carrier_account.

bill_to_name
string or null
bill_to_address_1
string or null
bill_to_address_2
string or null
bill_to_city
string or null
bill_to_state
string or null
bill_to_zip
string or null

Responses

Request samples

Content type
application/json
{
  • "customer_po_number": "string",
  • "manufacturer_auth": "string",
  • "freight_terms": "string",
  • "carrier_account": "string",
  • "shipping_point": "string",
  • "special_instructions": "string",
  • "priority": "normal",
  • "ship_via_id": "6a32d3b2-64b8-40df-becf-ad404313c722",
  • "ship_method": "string",
  • "ship_via_other": "string",
  • "third_party_payer_id": "22660371-5b32-496f-88ee-ca4422325933",
  • "bill_to_name": "string",
  • "bill_to_address_1": "string",
  • "bill_to_address_2": "string",
  • "bill_to_city": "string",
  • "bill_to_state": "string",
  • "bill_to_zip": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Release an order

Transitions an entered order to released. Releasing also stamps the per-supply-partner release sequence and the global original-release sequence on the order. Requires orders.release permission. Manufacturer- scoped API keys can only release orders on their own supply partner.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Cancel an order

Transitions an order to cancelled. Allowed from any pre-shipped status. Requires orders.edit permission and (for scoped keys) matching supply partner.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Close a shipped order

Transitions a shipped order to closed. Closed orders are read-only. Requires orders.edit permission and (for scoped keys) matching supply partner.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Set order priority

Sets the order priority to one of normal, high, or rush. Priority is editable until the order reaches a terminal status (shipped/billed/ closed/cancelled). Requires orders.edit permission and (for scoped keys) matching supply partner.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
priority
required
string
Enum: "normal" "high" "rush"

Responses

Request samples

Content type
application/json
{
  • "priority": "normal"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Record LTL freight carrier + PRO# on a shipped order

Records (or clears) the LTL freight carrier and PRO# on a post-ship order. LTL freight info — signed Bill of Lading, PRO# — arrives after the truck leaves and falls outside the parcel-carrier tracking ingestion (UPS/FedEx). Allowed only on orders in a post-ship state (shipped, partial, closed). Both body fields are optional and recorded independently; submitting both blank clears any prior record. Requires orders.edit permission and (for scoped keys) matching supply partner.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
carrier
string <= 120 characters

Free-text LTL freight carrier name (e.g. "Old Dominion").

pro_number
string <= 120 characters

Freight PRO# / tracking number.

Responses

Request samples

Content type
application/json
{
  • "carrier": "string",
  • "pro_number": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Add a line to an order (Phase 3e)

Adds one line. 422 exception_blocked when the item has an open pick exception (audit in progress); re-POST with acknowledge_exception=true to override. 422 negative_stock when warehouse allocation would drive qty_available negative; re-POST with acknowledge_negative=true to override. 422 invalid_split when warehouse + direct ≠ qty_ordered. Direct-ship lines never trigger exception_blocked or negative_stock. Returns 201 with the reloaded order + lines.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
item_id
required
string <uuid>
qty_ordered
required
integer [ 1 .. 100000000 ]
unit_price
number [ 0 .. 99999999 ]

400 invalid_price outside 0..99,999,999

discount_pct
number
fulfillment_source
string
Default: "warehouse"
Enum: "warehouse" "direct" "split"
qty_warehouse
integer
qty_direct
integer
acknowledge_negative
boolean
Default: false
acknowledge_exception
boolean
Default: false

Override the open-pick-exception gate (#1352)

Responses

Request samples

Content type
application/json
{
  • "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",
  • "qty_ordered": 1,
  • "unit_price": 0,
  • "discount_pct": 0,
  • "fulfillment_source": "warehouse",
  • "qty_warehouse": 0,
  • "qty_direct": 0,
  • "acknowledge_negative": false,
  • "acknowledge_exception": false
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Remove an order line (Phase 3e)

Returns 204 on success. 404 when the line does not belong to the URL's order, or when either order or line does not exist. 422 invalid_status when the order is no longer editable.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
lineID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Stamp direct-ship notify (via=email) (Phase 3e)

Stamps direct_notified_* on the supplied lines (which must all belong to the URL's order). The service emits an outbox event for the integration lane (n8n / external mailer) — this endpoint does not send email itself, matching the web flow.

note is accepted for symmetry with the web form but not persisted; the web embeds it client-side into the mailto URL only.

Refused (400 no_email_contact) when the order's supply partner lacks a direct-ship contact with an email — same gate the web enforces.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
line_ids
required
Array of strings <uuid> [ items <uuid > ]
note
string

Accepted for forward-compat; not persisted.

Responses

Request samples

Content type
application/json
{
  • "line_ids": [
    ],
  • "note": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Stamp direct-ship notify (via=out_of_band) (Phase 3e)

Same shape as /direct-ship/notify but for via=out_of_band (stamps the partner was notified by phone, in-person, etc.). Skips the email contact validation. note is accepted but not persisted.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
line_ids
required
Array of strings <uuid> [ items <uuid > ]
note
string

Responses

Request samples

Content type
application/json
{
  • "line_ids": [
    ],
  • "note": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Customers

Customer accounts (not supply-partner-scoped)

List customers

Paginated list of customers. Customers are global (not supply-partner-scoped).

Authorizations:
BearerAuth
query Parameters
q
string
status
string
page
integer
Default: 1
per_page
integer <= 100
Default: 50

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Create a customer

Creates a new customer record. Requires customers.edit permission. Customers are global — no supply partner scoping applies.

Authorizations:
BearerAuth
Request Body schema: application/json
required
account_number
required
string

Unique account identifier; service rejects duplicates with 422

name
required
string
customer_type_code
string
region
string
address_line_1
string
address_line_2
string
city
string
state
string
zip_code
string

Responses

Request samples

Content type
application/json
{
  • "account_number": "string",
  • "name": "string",
  • "customer_type_code": "string",
  • "region": "string",
  • "address_line_1": "string",
  • "address_line_2": "string",
  • "city": "string",
  • "state": "string",
  • "zip_code": "string"
}

Response samples

Content type
application/json
{
  • "data": { }
}

Search customers

Authorizations:
BearerAuth
query Parameters
q
required
string
per_page
integer <= 100
Default: 50

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Get customer detail

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": { }
}

Update a customer

Updates an existing customer. Requires customers.edit permission. Empty strings clear NULLable fields (matches the web form's NULLIF behavior). The account_number and status fields are not editable through this endpoint — manage those through dedicated lifecycle operations.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
name
required
string
customer_type_code
string
region
string
address_line_1
string
address_line_2
string
city
string
state
string
zip_code
string

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "customer_type_code": "string",
  • "region": "string",
  • "address_line_1": "string",
  • "address_line_2": "string",
  • "city": "string",
  • "state": "string",
  • "zip_code": "string"
}

Response samples

Content type
application/json
{
  • "data": { }
}

Create a drop-ship location (Phase 3b)

Creates a third-party ship-to address parented to this customer. Not manufacturer-scoped — customer is the parent. State and Country are uppercased to match the web form.

Returns 422 invalid_drop_ship_location with a semicolon-joined message when service validation rejects (missing name, address line, city, state, or zip).

The web's ?modal=1 order-entry-modal flow returns 204 + an HX-Trigger event; the API is JSON-only and always returns the created entity.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
name
required
string
attention
string
address_line_1
required
string
address_line_2
string
city
required
string
state
required
string

Uppercased to match web form.

zip_code
required
string
country
string
Default: "US"

Uppercased to match web form.

phone
string
email
string
notes
string

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "attention": "string",
  • "address_line_1": "string",
  • "address_line_2": "string",
  • "city": "string",
  • "state": "string",
  • "zip_code": "string",
  • "country": "US",
  • "phone": "string",
  • "email": "string",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a drop-ship location (Phase 3b)

PATCH semantics: omitted fields preserve. Cross-customer attempts (a loc_id that belongs to a different customer than the URL id) return 404 to avoid leaking existence.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
loc_id
required
string <uuid>
Request Body schema: application/json
required
name
string
attention
string
address_line_1
string
address_line_2
string
city
string
state
string
zip_code
string
country
string
phone
string
email
string
notes
string

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "attention": "string",
  • "address_line_1": "string",
  • "address_line_2": "string",
  • "city": "string",
  • "state": "string",
  • "zip_code": "string",
  • "country": "string",
  • "phone": "string",
  • "email": "string",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Deactivate a drop-ship location (Phase 3b)

Hides the location from order-entry pickers. Existing orders' FKs are preserved.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
loc_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Reactivate a drop-ship location (Phase 3b)

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
loc_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Create a third-party freight payer (#1201)

Creates a freight bill-to party parented to this customer, used when an order's freight terms are "3RD PARTY". Not manufacturer-scoped — customer is the parent. State and Country are uppercased to match the web form.

Returns 422 invalid_third_party_payer with a semicolon-joined message when service validation rejects (missing name, address line, city, state, zip, or carrier account #).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
name
required
string
attention
string
address_line_1
required
string
address_line_2
string
city
required
string
state
required
string

Uppercased to match web form.

zip_code
required
string
country
string
Default: "US"

Uppercased to match web form.

carrier_account
required
string

UPS/FedEx account the freight is billed to.

notes
string

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "attention": "string",
  • "address_line_1": "string",
  • "address_line_2": "string",
  • "city": "string",
  • "state": "string",
  • "zip_code": "string",
  • "country": "US",
  • "carrier_account": "string",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a third-party payer (#1201)

PATCH semantics: omitted fields preserve. Cross-customer attempts (a payer_id that belongs to a different customer than the URL id) return 404 to avoid leaking existence.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
payer_id
required
string <uuid>
Request Body schema: application/json
required
name
string
attention
string
address_line_1
string
address_line_2
string
city
string
state
string
zip_code
string
country
string
carrier_account
string
notes
string

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "attention": "string",
  • "address_line_1": "string",
  • "address_line_2": "string",
  • "city": "string",
  • "state": "string",
  • "zip_code": "string",
  • "country": "string",
  • "carrier_account": "string",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Deactivate a third-party payer (#1201)

Hides the payer from order-entry pickers. Existing orders' FKs are preserved.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
payer_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Reactivate a third-party payer (#1201)

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
payer_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

API Keys

API key management (requires admin.api_keys permission)

List all API keys

Requires admin.api_keys permission.

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Create a new API key

Generates a new API key. The plaintext key is returned once in the response. Validates that requested scopes are within both the caller's permissions and the target user's current role permissions.

Authorizations:
BearerAuth
Request Body schema: application/json
required
user_id
required
string <uuid>
name
required
string
scopes
required
Array of strings

Permission codes (e.g. orders.view, inventory.edit)

expires_at
string

RFC3339 or YYYY-MM-DD (optional)

Responses

Request samples

Content type
application/json
{
  • "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
  • "name": "string",
  • "scopes": [
    ],
  • "expires_at": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Revoke an API key

Soft-deletes the key (sets is_active = false). Cannot be undone.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Contacts

People associated with supply partners or customers. A single contact can be linked to multiple entities, each with an optional role string (e.g. direct_ship, billing). Used by features like the direct-ship notify button to look up the right person to email.

List active contacts for an entity

Returns the active contacts associated with the given entity, each bundled with its association id + role. Inactive contacts are filtered out. Order: primary first, then last name. Requires the parent entity's *.view permission.

Authorizations:
BearerAuth
path Parameters
entityType
required
string
Enum: "manufacturer" "customer"
entityID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Create a contact and link it to an entity

Creates a new contact and an association in one transaction. Requires the parent entity's *.edit permission.

Authorizations:
BearerAuth
path Parameters
entityType
required
string
Enum: "manufacturer" "customer"
entityID
required
string <uuid>
Request Body schema: application/json
required
first_name
string or null
last_name
required
string
title
string or null
email
string or null
phone
string or null
mobile
string or null
is_primary
boolean
Default: false
notes
string or null
role
string

Optional role on the association created with this contact. Free-form, but the well-known values listed in the Contacts tag are what downstream features look up.

Responses

Request samples

Content type
application/json
{
  • "first_name": "string",
  • "last_name": "string",
  • "title": "string",
  • "email": "string",
  • "phone": "string",
  • "mobile": "string",
  • "is_primary": false,
  • "notes": "string",
  • "role": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "first_name": "string",
  • "last_name": "string",
  • "title": "string",
  • "email": "string",
  • "phone": "string",
  • "mobile": "string",
  • "is_primary": true,
  • "status": "active",
  • "notes": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "created_by": "ee824cad-d7a6-4f48-87dc-e8461a9201c4",
  • "updated_by": "deea00dc-b6b6-4412-a483-26ac61e1f6fe",
  • "association_id": "b7261822-545a-4eed-9acd-2428afa0245c",
  • "role": "string"
}

Get a single contact by id

Returns the contact (active or inactive). Authorization: caller must have view perm on at least one entity the contact is associated with.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "first_name": "string",
  • "last_name": "string",
  • "title": "string",
  • "email": "string",
  • "phone": "string",
  • "mobile": "string",
  • "is_primary": true,
  • "status": "active",
  • "notes": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "created_by": "ee824cad-d7a6-4f48-87dc-e8461a9201c4",
  • "updated_by": "deea00dc-b6b6-4412-a483-26ac61e1f6fe"
}

Update a contact's writable fields

Mutates name, title, email, phone, mobile, is_primary, notes. Status flips and per-association role changes go through their own endpoints. Authorization: caller must have edit perm on at least one entity the contact is linked to.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
first_name
string or null
last_name
required
string
title
string or null
email
string or null
phone
string or null
mobile
string or null
is_primary
boolean
notes
string or null

Responses

Request samples

Content type
application/json
{
  • "first_name": "string",
  • "last_name": "string",
  • "title": "string",
  • "email": "string",
  • "phone": "string",
  • "mobile": "string",
  • "is_primary": true,
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "first_name": "string",
  • "last_name": "string",
  • "title": "string",
  • "email": "string",
  • "phone": "string",
  • "mobile": "string",
  • "is_primary": true,
  • "status": "active",
  • "notes": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "created_by": "ee824cad-d7a6-4f48-87dc-e8461a9201c4",
  • "updated_by": "deea00dc-b6b6-4412-a483-26ac61e1f6fe"
}

Soft-delete a contact (status = inactive)

Sets status to inactive. Associations stay in the database but the contact disappears from list endpoints and LookupByRole.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Restore a previously deactivated contact

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Drop a single contact-entity association

Removes the link between a contact and one entity. The contact and its other associations survive. Authorization: caller must have edit perm on the entity the association points to.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Receiving

Inbound consignment receiving. Batches go through draft → verified → posted (or voided), with optional QC inspection on individual lines. The bulk-import endpoint accepts a parsed payload directly so n8n / Repfabric / scripts don't have to walk the spreadsheet upload flow.

List receiving batches (#1396)

Paginated batch list, newest first. Requires receiving.view. Supply-partner-scoped actors are forced to their own supply partner regardless of the manufacturer param.

Authorizations:
BearerAuth
query Parameters
status
string
Enum: "draft" "verified" "posted" "voided"

Repeatable.

manufacturer
string <uuid>
from
string <date>

received_date lower bound (YYYY-MM-DD).

to
string <date>

received_date upper bound (inclusive).

page
integer >= 1
per_page
integer [ 1 .. 100 ]

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Create a draft receiving batch

Creates a new draft receiving batch for a supply partner. Lines are added separately via POST /api/v1/receiving/{id}/lines. Requires receiving.create permission. Manufacturer-scoped keys can only create batches for their own supply partner.

Authorizations:
BearerAuth
Request Body schema: application/json
required
manufacturer_id
required
string <uuid>
received_date
string <date>

YYYY-MM-DD. Omit to default to today in the server's local timezone (#970). Foreign callers that don't share the server's timezone should send this explicitly to avoid off-by-one-day surprises near midnight.

notes
string or null

Responses

Request samples

Content type
application/json
{
  • "manufacturer_id": "cafecd41-5060-405f-a23d-b38c40f09e3f",
  • "received_date": "2019-08-24",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Bulk-import a receiving batch from a parsed payload

JSON twin of the spreadsheet-import flow. Caller hands us already- resolved item_ids and qtys; we create a draft batch and write each row with import semantics (qty_received == qty_expected, so the variance display lights up if a line is later edited). Lines that fail validation are reported in skipped with their array index + reason — caller can DELETE the partial batch and retry. Requires inventory.import permission. Manufacturer-scoped keys can only import for their own supply partner.

Authorizations:
BearerAuth
Request Body schema: application/json
required
manufacturer_id
required
string <uuid>
received_date
string <date>

YYYY-MM-DD. Omit to default to today in the server's local timezone (#970). Foreign callers that don't share the server's timezone should send this explicitly to avoid off-by-one-day surprises near midnight.

notes
string or null
required
Array of objects

Responses

Request samples

Content type
application/json
{
  • "manufacturer_id": "cafecd41-5060-405f-a23d-b38c40f09e3f",
  • "received_date": "2019-08-24",
  • "notes": "string",
  • "lines": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a supply partner's saved import mapping profile

The saved column mapping the receiving-import wizard applies to this supply partner's manifest uploads: which worksheet and header row their files use, and which header text carries each canonical receiving field. Requires inventory.import permission. Manufacturer-scoped keys can only read their own supply partner's profile (cross-partner requests 404).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Supply partner (manufacturer) id.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Create or replace a supply partner's import mapping profile

Full replace (upsert) — the same last-write-wins semantics as saving from the mapping wizard. column_map keys are the canonical fields (item, qty, ref, cost, uom); item and qty are required; values are the source file's header text. Requires inventory.import permission and the same manufacturer scoping as GET.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Supply partner (manufacturer) id.

Request Body schema: application/json
required
sheet_name
string or null

Worksheet name; omit/null for the first sheet (or CSV files).

header_row
required
integer >= 1
required
object

Canonical field → source header text.

Responses

Request samples

Content type
application/json
{
  • "sheet_name": "string",
  • "header_row": 1,
  • "column_map": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a supply partner's import mapping profile

Removes the saved mapping; the next manifest upload for this supply partner drops back into the mapping wizard. Requires inventory.import permission and the same manufacturer scoping as GET.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Supply partner (manufacturer) id.

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Get a receiving batch with its lines (#1396)

Requires receiving.view. Supply-partner-scoped actors get 404 for another supply partner's batch.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a draft receiving batch

Deletes a draft batch and all its lines. Requires receiving.create permission. Returns 422 invalid_status if the batch is not draft.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Add a line to a draft batch

Appends a line. Requires receiving.create permission and a draft batch. The item must belong to the same supply partner as the batch (422 manufacturer_mismatch otherwise).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
item_id
required
string <uuid>
qty_received
required
integer >= 1
reference_number
string
reference_type
string
new_unit_cost
number or null <float>
new_location_id
string or null <uuid>

Responses

Request samples

Content type
application/json
{
  • "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",
  • "qty_received": 1,
  • "reference_number": "string",
  • "reference_type": "string",
  • "new_unit_cost": 0.1,
  • "new_location_id": "8bffbbd8-e210-4e13-9881-83888e111d8a"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a draft line

Edits qty_received, reference_number, new_unit_cost, and/or new_location_id. Requires receiving.create permission and a draft batch. Returns the reloaded {batch, lines} so callers see the post-update view.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
lineID
required
string <uuid>
Request Body schema: application/json
required
qty_received
required
integer >= 1
reference_number
string
new_unit_cost
number or null <float>
new_location_id
string or null <uuid>

Responses

Request samples

Content type
application/json
{
  • "qty_received": 1,
  • "reference_number": "string",
  • "new_unit_cost": 0.1,
  • "new_location_id": "8bffbbd8-e210-4e13-9881-83888e111d8a"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Remove a line from a draft batch

Requires receiving.create permission and a draft batch.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
lineID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Record QC inspection results for a line

Records pass/fail counts. Requires receiving.qc permission and a verified batch. qty_passed + qty_failed must equal the line's qty_received (400 bad_request on mismatch). Returns the reloaded {batch, lines} so callers see the new inspection state.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
lineID
required
string <uuid>
Request Body schema: application/json
required
qty_passed
required
integer >= 0
qty_failed
required
integer >= 0
inspection_notes
string

Responses

Request samples

Content type
application/json
{
  • "qty_passed": 0,
  • "qty_failed": 0,
  • "inspection_notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Pass-all-qty inspection (toggle)

Marks a line fully passed (qty_passed = qty_received). If the line is already fully passed, the same call reverts it to pending — same toggle semantics as the mobile swipe-right gesture. Requires receiving.qc permission and a verified batch.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
lineID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Waive QC inspection for a line

Marks a line's inspection as waived, clearing any prior inspection data. Requires receiving.qc permission and a verified batch.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
lineID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Mark a draft batch as verified (ready to inspect / post)

Transitions draft → verified. Empty batches are refused (422 batch_empty). Requires receiving.inspect permission.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Revert a verified batch to draft

Transitions verified → draft. Clears all line inspection data so QC can be redone after edits. Requires receiving.inspect permission.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Post a verified batch to inventory

High-stakes audit-trail event: in a single transaction, increments on-hand quantities, writes inventory_movements + item_history, upserts item_locations, and stamps received/last-movement dates. QC-failed quantities go to the quarantine location instead of available stock. Refuses any line still in pending inspection status (422 pending_qc) — inspect or waive each line first. Requires receiving.post permission.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Void a posted batch

Reverses the inventory effect of a posted batch by writing compensating movement / history rows. Cost calculations are NOT reversed (subsequent transactions may have already shifted average_cost in ways that cannot be cleanly undone). Requires receiving.post permission. reason is required (non-empty).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
reason
required
string non-empty

Responses

Request samples

Content type
application/json
{
  • "reason": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Set the same location on every line of a draft batch

Bulk-set the new_location_id on all lines of a draft batch. Requires receiving.create permission and an active warehouse location.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
location_id
required
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "location_id": "46910cc3-ab41-4b80-b4a7-94dab9f1b795"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Cycle Counts

Warehouse-wide cycle count batches. Generate sample, start, record counts, approve variances, complete. Refused for manufacturer-scoped keys — the random sample crosses supply-partner lines.

List cycle-count batches (#1396)

Paginated batch list, newest first. Requires cycle_counts.view. Cycle counts are warehouse-wide — supply-partner-scoped actors are refused with 403, matching the write endpoints.

Authorizations:
BearerAuth
query Parameters
status
string
Enum: "draft" "in_progress" "completed" "cancelled"
page
integer >= 1
per_page
integer [ 1 .. 100 ]

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Generate a cycle count batch

Creates a new draft batch with a random sample of items. Manufacturer-scoped keys are refused (sample crosses supply-partner lines). Requires cycle_counts.count permission.

Authorizations:
BearerAuth
Request Body schema: application/json
optional
num_items
integer >= 1
Default: 10

Responses

Request samples

Content type
application/json
{
  • "num_items": 10
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a cycle-count batch with its lines (#1396)

Batch header plus every line with counted quantities and variances. Requires cycle_counts.view; supply-partner-scoped actors are refused with 403.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Mark a cycle count batch as in_progress

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Record a counted quantity for a line

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
line_id
required
string <uuid>
counted_qty
required
integer >= 0

Responses

Request samples

Content type
application/json
{
  • "line_id": "6cb02bfb-65d4-467b-864d-461e923db664",
  • "counted_qty": 0
}

Response samples

Content type
application/json
{
  • "error": {
    }
}

Approve a counted line and adjust inventory

Requires cycle_counts.approve permission.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
lineID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Mark a cycle count batch as completed

Requires cycle_counts.approve permission.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Inventory

Item-level inventory adjustments and movement audit trail.

List inventory adjustments

The review ledger behind /inventory/adjustments (#1334): every inventory movement of type adjustment (manual) or cycle_count_correction, newest first, paginated. Requires inventory.view permission. Supply-partner-scoped keys only see their own supply partner's adjustments regardless of the manufacturer param. Unlike the web page, no default date window is applied — an unbounded call lists everything.

Authorizations:
BearerAuth
query Parameters
from
string <date>

Inclusive lower bound on performed_at (yyyy-mm-dd).

to
string <date>

Inclusive upper bound on performed_at (yyyy-mm-dd; widened to end of day).

manufacturer
string <uuid>

Filter by supply partner ID (ignored for supply-partner-scoped keys).

q
string

Item search — normalized part-number prefix or display-form substring.

performed_by
string <uuid>

Filter by the user who recorded the movement.

reason
string

Exact match on the stored reason token (e.g. damaged, found_stock).

type
string
Enum: "adjustment" "cycle_count_correction"

Movement type; omit for both.

page
integer
Default: 1
per_page
integer <= 100
Default: 50

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Adjust an item's qty_on_hand

Atomically increments item qty_on_hand by the (signed) quantity, keeps item_locations totals in sync, writes a chained inventory_movement and an item_history row. Reason is required and appears on the movement record. Requires inventory.adjust permission. Manufacturer-scoped keys can only adjust items that belong to their own supply partner.

Authorizations:
BearerAuth
Request Body schema: application/json
required
item_id
required
string <uuid>
quantity
required
integer [ -100000000 .. 100000000 ]

Signed delta (positive adds stock, negative removes). Must be non-zero and within ±100,000,000.

reason
required
string non-empty

Responses

Request samples

Content type
application/json
{
  • "item_id": "4d8cd62e-a579-4dae-af8c-3172f96f8f7c",
  • "quantity": -100000000,
  • "reason": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List inventory movements (#1396)

The full inventory-movement ledger — every movement type, not just the adjustment pair served by /inventory/adjustments. Newest first, paginated, no default date window. Requires inventory.view. Supply-partner-scoped actors are forced to their own supply partner. type is repeatable; an unknown value is a 400 (never silently dropped — on a full ledger that would return everything).

Authorizations:
BearerAuth
query Parameters
type
string
Enum: "receipt" "shipment" "adjustment" "transfer" "return" "cycle_count_correction"

Repeatable movement-type filter; omit for all types.

manufacturer
string <uuid>
q
string

Item number prefix / substring match.

performed_by
string <uuid>
reason
string
from
string <date>

performed_at lower bound (YYYY-MM-DD).

to
string <date>

performed_at upper bound (inclusive).

page
integer >= 1
per_page
integer [ 1 .. 100 ]

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Exceptions

Office-side review of pick exceptions (short picks, mispicks) and quarantine notes for failed-QC receiving lines.

List pick exceptions (#1416)

Short-pick, mispick, and quality-flag exceptions in one list, newest first. Requires orders.view. Supply-partner-scoped actors are forced to their own supply partner (an explicit manufacturer is ignored).

No state means every state — unlike the office queue, which defaults to open. Paging is applied in memory over the merged result (the three kinds live in separate tables and are unioned after the query); volume is small enough that this is deliberate.

Authorizations:
BearerAuth
query Parameters
type
string
Enum: "short_pick" "mispick" "quality"
state
string
Enum: "open" "reviewed" "superseded"
order
string <uuid>

Order UUID. Matches exceptions on any pick batch for the order, batch-level quality flags included.

manufacturer
string <uuid>

Supply partner UUID. Unscoped actors only; scoped keys are pinned to their own.

awaiting_supply_partner
string
Value: "1"

1 keeps only exceptions whose open block is parked on the supply partner.

from
string <date>

Inclusive calendar day (UTC) on the exception's created_at.

to
string <date>

Inclusive calendar day (UTC) on the exception's created_at.

page
integer
Default: 1
per_page
integer <= 100
Default: 25

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Short-pick exception detail (#1416)

Requires orders.view. 404 for a mispick or quality id (each kind has its own table and route), for another supply partner's exception, and for a non-UUID id.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Mispick exception detail (#1416)

Same contract as GET /api/v1/exceptions/{id} for a mispick id.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Quality-flag exception detail (#1416)

Same contract as GET /api/v1/exceptions/{id} for a quality-flag id. block is null for non-blocking reasons (e.g. label_illegible).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Mark a short-pick exception reviewed

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
optional
note
string

Responses

Request samples

Content type
application/json
{
  • "note": "string"
}

Response samples

Content type
application/json
{
  • "error": {
    }
}

Mark a mispick exception reviewed

disposition is one of: repick_required (reopens the pick line), ship_as_is, cancel_line, other.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
disposition
required
string
Enum: "repick_required" "ship_as_is" "cancel_line" "other"
note
string

Responses

Request samples

Content type
application/json
{
  • "disposition": "repick_required",
  • "note": "string"
}

Response samples

Content type
application/json
{
  • "error": {
    }
}

Mark a quality-flag exception reviewed (#1137)

Office triage of a quality flag (damaged carton, broken seal, wrong packaging, illegible label, other). Blocking flags (damaged_in_pack, damaged_from_mfg) route through the order_blocks resolution machinery — review with ship_as_is, cancel_order, or other. Non-blocking flags get the same disposition vocab; reviewing only stamps the exception row.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
disposition
required
string
Enum: "ship_as_is" "cancel_order" "other"
note
string

Responses

Request samples

Content type
application/json
{
  • "disposition": "ship_as_is",
  • "note": "string"
}

Response samples

Content type
application/json
{
  • "error": {
    }
}

Update inspection_notes on a quarantined receiving line

The one allowed mutation on a posted receiving_line. Photos are attached separately via POST /api/v1/documents/receiving_line/{id}. Requires receiving.qc permission.

Authorizations:
BearerAuth
path Parameters
lineID
required
string <uuid>
Request Body schema: application/json
required
inspection_notes
string

Responses

Request samples

Content type
application/json
{
  • "inspection_notes": "string"
}

Response samples

Content type
application/json
{
  • "error": {
    }
}

Shipping

Per-batch tracking edits and bulk shipment-tracking import. The bulk import takes a CSV file and applies it in one shot — ambiguous rows or unconfirmed overwrites force a 422 with row-level details.

List shipment-completion events (tenant-scoped, idempotent)

Cursor-based pull surface for downstream commission, ICM, and sales-activity consumers (Repfabric n8n connector and similar). Distinct from /api/v1/events:

  • Tenant-scoped. Manufacturer-scoped API keys see only their own supply partner's shipments. Admin keys see all tenants and may filter via ?manufacturer_id=. A scoped key passing ?manufacturer_id=<other> is rejected with 403 — no silent re-scoping.

  • Idempotent. No ack mutation; re-issuing the same ?since= returns the same window. Consumers dedupe by event_id.

  • Filtered to shipment-bearing event types. Today order.shipped.v1 only; future shipment-bearing types are added via the events.ShipmentEventTypes allowlist.

Order: ascending (occurred_at, id). The next_cursor field is non-null whenever the result page filled the limit; pass it back as ?since=<cursor> to fetch the next page.

Note: aggregate_id IS the shipment identifier in CT's data model (a shipment is an order in terminal shipped status). There is no separate shipments aggregate today.

Requires orders.view permission.

Authorizations:
BearerAuth
query Parameters
limit
integer [ 1 .. 500 ]
Default: 100

Page size; defaults to 100, clamped to 500.

since
string

Either an RFC3339 occurred_at floor (inclusive) or an opaque continuation cursor returned by a prior call's next_cursor. The two forms are auto-detected; consumers can ignore the distinction and treat next_cursor as a string to round-trip.

manufacturer_id
string <uuid>

Restrict results to one supply partner. Admin keys may scope this way; manufacturer-scoped keys may pass it but only with their own scope (mismatched scope returns 403).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": "string"
}

Set or correct the tracking number / carrier on a pick batch

Office correction path for shipment tracking. Carrier auto-detects from the tracking number when omitted. Requires orders.edit or orders.tracking_correct permission.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
tracking_number
required
string non-empty
carrier
string
Enum: "ups" "fedex" "usps" "other"

Responses

Request samples

Content type
application/json
{
  • "tracking_number": "string",
  • "carrier": "ups"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List the boxes (parcels) on a shipment

Returns the live boxes on a pick batch, each with its own tracking number + carrier, ordered by box sequence. Requires orders.view permission. A shipment with no captured boxes returns an empty list.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Add a box (parcel) to a shipment

Appends a box with its own carrier tracking number. Carrier auto-detects from the tracking number when omitted. Idempotent on (carrier, tracking_number) — re-posting the same number returns the existing box instead of creating a duplicate. Requires orders.ship permission. This is the documented equivalent of the desktop ship-station "Add box" affordance.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
tracking_number
required
string non-empty
carrier
string
Enum: "ups" "fedex" "usps" "other"

Responses

Request samples

Content type
application/json
{
  • "tracking_number": "string",
  • "carrier": "ups"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Correct one box's tracking number / carrier

Office correction for a single box. Carrier auto-detects when omitted. Requires orders.edit or orders.tracking_correct permission. Returns 404 when the parcel does not belong to the batch (no ID enumeration).

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
parcelID
required
string <uuid>
Request Body schema: application/json
required
tracking_number
required
string non-empty
carrier
string
Enum: "ups" "fedex" "usps" "other"

Responses

Request samples

Content type
application/json
{
  • "tracking_number": "string",
  • "carrier": "ups"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Void (remove) a box from a shipment

Soft-voids one box (the row is retained for audit; carrier-event replays stay idempotent). Requires orders.edit or orders.tracking_correct permission. Returns 404 when the parcel does not belong to the batch.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
parcelID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Bulk-import shipment tracking from a CSV

One-shot parse + match + stage + commit. Skips the web's preview step — n8n / scripts dedupe upstream. Multipart upload (50 MB cap). If any row matches multiple pick batches the endpoint returns 422 ambiguous_rows with a import_id + per-row details so the caller can resolve duplicates and retry. If any row would overwrite an existing tracking number, returns 422 confirm_overwrites_required unless confirm_overwrites=true is set on the form.

Authorizations:
BearerAuth
Request Body schema: multipart/form-data
required
file
required
string <binary>

CSV file with order_reference + tracking_number columns.

confirm_overwrites
string

Pass true (or 1 / on) to allow overwriting existing tracking numbers.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Picking

Pick / pack / ship lifecycle for warehouse flows. API keys act as the user they were issued to — audit-log identity matches the UI session path. The ship endpoint collapses three web routes (mobile ship + desktop confirm + ship-with-existing) by treating tracking as optional: provide it to capture-then-ship, omit to ship with whatever's already on the batch.

Capture tracking on a pick batch (mobile-scan twin)

Auto-detects carrier from the tracking-number format. The validator (orders.ValidateForCapture) requires the tracking number to match a known UPS / FedEx / USPS pattern after whitespace + dash normalization; SSCC pallet IDs, employee badge codes, sub-10 / >40-character strings, and anything outside [A-Z0-9]{10,40} all return 422 invalid_tracking with a field-level message in the response body. See also the office-correction endpoint PATCH /api/v1/pick-batches/{batchID}/tracking (Phase 2b) which accepts an explicit carrier and is used by office staff after the fact — that path is intentionally lenient (the office is the escape hatch for unusual carriers).

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
tracking_number
required
string non-empty

Responses

Request samples

Content type
application/json
{
  • "tracking_number": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List an order's pick batches (#1396)

Every pick batch on the order in every state, with tracking and parcel detail, newest-packed first. Requires orders.view. Supply-partner- scoped actors get 404 for another supply partner's order.

Authorizations:
BearerAuth
path Parameters
orderID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Start (or resume) a pick batch for an order

Resumes the existing in-progress pick batch for the order, or creates a new one if none exists. Requires orders.ship permission and access to the order's manufacturer.

Authorizations:
BearerAuth
path Parameters
orderID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a pick batch (#1396)

Pick-batch detail in the pick-session shape - lines with picked/short state, tracking, parcels, staging. Requires orders.view (the write endpoints require orders.ship). Supply-partner-scoped actors get 404 for another supply partner's batch.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Record a successful pick on a line

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
order_line_id
required
string <uuid>
qty_picked
required
integer >= 1

Must equal the line's qty_to_pick exactly — partial quantities go through the short-pick endpoint. Mismatches return 400 qty_mismatch; an already-picked or short line returns 409 line_not_pickable.

Responses

Request samples

Content type
application/json
{
  • "order_line_id": "bc9d0c12-1a56-474d-bb91-af19b2f06cc4",
  • "qty_picked": 1
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Record a short pick (qty_actual < qty_to_pick) with reason

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
order_line_id
required
string <uuid>
qty_actual
required
integer >= 0

Quantity actually pulled; must be between 0 and qty_to_pick - 1. Negative values return 400 qty_mismatch.

reason
required
string non-empty

Responses

Request samples

Content type
application/json
{
  • "order_line_id": "bc9d0c12-1a56-474d-bb91-af19b2f06cc4",
  • "qty_actual": 0,
  • "reason": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Undo a short-pick the picker just recorded

Reverts a short-pick exception while it is still 'open'. Refused once the office has reviewed the exception (409 already_reviewed).

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
pblID
required
string <uuid>
Request Body schema: application/json
optional
superseded_reason
string

One of found_it, miscounted_corrected, or other.

Responses

Request samples

Content type
application/json
{
  • "superseded_reason": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Record a mispick exception

Flags a wrong-item pick. Photo evidence is uploaded separately via POST /api/v1/documents/mispick_exception/{exception_id} after the exception_id is returned from this call.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
order_line_id
required
string <uuid>
flag_reason
required
string
Enum: "wrong_item_known" "wrong_item_unknown" "other"
flag_note
string
actual_item_id
string or null <uuid>
actual_qty
integer or null >= 0

Responses

Request samples

Content type
application/json
{
  • "order_line_id": "bc9d0c12-1a56-474d-bb91-af19b2f06cc4",
  • "flag_reason": "wrong_item_known",
  • "flag_note": "string",
  • "actual_item_id": "8d10fb12-55cd-4b32-b539-16e03f664409",
  • "actual_qty": 0
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Undo an open mispick flag

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
pblID
required
string <uuid>
Request Body schema: application/json
optional
superseded_reason
string

Defaults to found_correct_item when omitted.

Responses

Request samples

Content type
application/json
{
  • "superseded_reason": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Record a quality-flag exception (#1137)

Flags a condition issue (damage, broken seal, wrong packaging, illegible label, other) at pack stage. Distinct from mispick (wrong identity) and short-pick (wrong count). Photo evidence is uploaded separately via POST /api/v1/documents/quality_exception/{exception_id} after the exception_id is returned from this call. Omit order_line_id for a batch-level flag (a finding that covers the whole shipment, not one specific line). Blocking reasons (damaged_in_pack, damaged_from_mfg) create an order_blocks row in the same tx; non-blocking reasons leave pack/ship ungated.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
order_line_id
string or null <uuid>

Omit (or null) for a batch-level flag.

flag_reason
required
string
Enum: "damaged_in_pack" "damaged_from_mfg" "packaging_wrong" "label_illegible" "other"
flag_note
string

Required when flag_reason is other.

Responses

Request samples

Content type
application/json
{
  • "order_line_id": "bc9d0c12-1a56-474d-bb91-af19b2f06cc4",
  • "flag_reason": "damaged_in_pack",
  • "flag_note": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Upload a proof-of-pack photo (#1149)

Attaches a photo to the pick_batch row as a happy-path record of "this is what went in the box." Distinct from #1137's quality-flag photos, which are tied to an open exception. Multiple photos allowed; the field is optional from the UI side (no error if a packer marks the batch packed without one). Refused with 409 once the batch is shipped or cancelled — post-ship the record is frozen, and the office attaches photos manually via the existing per-batch document upload route.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: multipart/form-data
required
photo
required
string <binary>

Image file, up to 50 MB. JPEG/PNG/etc.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Undo an open quality flag (#1137)

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
qualityID
required
string <uuid>
Request Body schema: application/json
optional
superseded_reason
string
Enum: "found_intact" "flag_in_error" "other"

Defaults to found_intact when omitted.

Responses

Request samples

Content type
application/json
{
  • "superseded_reason": "found_intact"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Skip a line (defer until later in the batch)

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
order_line_id
required
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "order_line_id": "bc9d0c12-1a56-474d-bb91-af19b2f06cc4"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Mark picking complete (status → completed)

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Record the pack-station QC tick for one line

Stamps pack_verified_at / pack_verified_by on the line the moment the packer confirms the goods match it (#1426), so the tick survives navigating away (flag, photo) and shows on any device. Idempotent — a repeat call keeps the original stamp. Only allowed while the batch is at the pack station (status = completed). Lines stamped here are exempt from verified_line_ids on /pick-batches/{batchID}/pack. The office reopening the line for a re-pick (mispick / block repick_required) clears the stamp. UI twin: the per-line box on the mobile pack screen.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
pblID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Clear the pack-station QC tick for one line

Clears pack_verified_at / pack_verified_by on the line (#1426). Same guards as /verify; clearing an already-clear line is a no-op 200. UI twin: unticking the per-line box on the mobile pack screen.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
pblID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Mark a batch as packed (every line verified; cartons only — weight moves to ship)

The pack station is the QC check (#1413): every line of the batch must have been checked against the goods before it packs. A line counts as verified when it was stamped earlier via POST /pick-batches/{batchID}/lines/{pblID}/verify (#1426) or when its Lines[].PickBatchLineID is listed in verified_line_ids here. Any line still unverified is refused with 422 lines_unverified and the batch stays completed; on success every line carries PackVerifiedAt, stamped in the same transaction as the status flip. Ids not on the batch are ignored. (The start-batch 201 carries empty PickBatchLineIDs — take them from any later lifecycle response, e.g. /complete.)

Carton count is captured at pack time. Weight is captured at the ship handoff (#1136) — see /api/v1/pick-batches/{batchID}/ship.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
cartons
required
integer [ 1 .. 50 ]

Carton count for the packed batch. #1120 caps the range to catch fat-finger entries on the mobile keypad.

verified_line_ids
Array of strings <uuid> [ items <uuid > ]

#1413: pick_batch_lines ids (Lines[].PickBatchLineID) confirmed against the goods at pack time. Optional since #1426 — lines already stamped via .../lines/{pblID}/verify are exempt, so this only needs to cover what is still unstamped. Extras are ignored.

moved_to_location_id
string or null <uuid>

#1377 Phase 3: optional carton destination — an active shipping-type location (lane). Omit to leave the batch where it was packed. If the lane is invalid the pack still applies and the call returns 422 lane_move_failed.

Responses

Request samples

Content type
application/json
{
  • "cartons": 1,
  • "verified_line_ids": [
    ],
  • "moved_to_location_id": "e12d6cf9-ebb1-4938-9f07-32dd6580465e"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Record which staging slot a completed batch physically sits on

Staging slots (#1377): after picking completes, the picked stack is placed on a labeled staging slot (a staging-type warehouse location) so the packer — or the will-call counter — can find it. A null or omitted location_id records the "handed directly to packer" escape path. Calling again re-stages (moves) the batch; the previous slot frees automatically. Only one live (completed) batch can occupy a slot at a time.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
location_id
string or null <uuid>

Active staging-type warehouse location. Null = handed directly to packer.

Responses

Request samples

Content type
application/json
{
  • "location_id": "46910cc3-ab41-4b80-b4a7-94dab9f1b795"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Confirm a cancelled-while-staged batch's stack was returned to the shelf

Restock confirmation (#1385): when an order is cancelled while its picked stack sits on a staging slot, the slot renders as "needs clearing" and a Return to shelf task surfaces until someone physically puts the stock back and confirms. This endpoint stamps restocked_at/restocked_by on the batch, freeing the slot for staging and clearing the task. No inventory numbers change — on-hand was never decremented (that happens at ship); this is a physical-handling confirmation, audit-logged against the order.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

List staging slots with live occupancy

Every staging-type warehouse location the slot grid shows: all active slots, plus retired slots still holding a live batch or an uncleared cancelled stack (visible until freed/cleared). Sorted naturally (S2 before S10). needs_clearing (#1385) marks a slot physically holding a cancelled batch's stack awaiting its return-to-shelf confirmation — not offered as a staging target.

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "slots": [
    ]
}

Ship a packed batch (collapsed mobile + desktop ship endpoint)

Collapses three web routes into a single API endpoint:

  • With tracking_number → captures tracking + confirms shipment (the mobile ship-with-scan + desktop ship-confirm flows).
  • Without a body → ships with whatever tracking is already on the batch (the desktop ship-with-existing flow). 422 missing_tracking if no tracking is present — caller can either supply tracking or POST /skip-shipping.

Carrier auto-detects from the tracking number when omitted. The auto-detect path runs orders.ValidateForCapture, so unrecognized labels (SSCC pallets, badges, sub-10 strings) return 422 invalid_tracking. When the caller supplies an explicit carrier, the auto-detect gate is skipped — the caller has taken responsibility for the carrier choice (same semantics as the office PATCH endpoint).

Optional weight_lbs captures shipped package weight at the carrier handoff (#1136). When > 0 it is recorded in the shipped audit-log payload; when omitted or 0 the audit payload carries no weight key.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
optional
tracking_number
string
carrier
string
Enum: "ups" "fedex" "usps" "other"
weight_lbs
integer >= 0

Responses

Request samples

Content type
application/json
{
  • "tracking_number": "string",
  • "carrier": "ups",
  • "weight_lbs": 0
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Flag for office and ship without tracking

Desktop ship-station "office follows up" path: flags the batch for office tracking entry and confirms the shipment. The office picks it up from /admin/shipments/pending-tracking.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

WILL CALL customer-pickup handoff

Customer is at the dock. Captures the picker-up name and (optional) notes; runs the same inventory move as ship-confirm but stamps pickup metadata on the order. Requires the order to have freight_terms='WILL CALL' and the batch to be in 'completed'.

#1371: a proof-of-pickup photo is REQUIRED — the batch must already have at least one photo uploaded via POST /api/v1/pick-batches/{batchID}/pickup-photo, else 400 pickup_photo_required.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: application/json
required
picked_up_by_name
required
string non-empty
notes
string

Responses

Request samples

Content type
application/json
{
  • "picked_up_by_name": "string",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Upload a proof-of-pickup photo (#1371)

Attaches a photo documenting the WILL CALL handoff (signed receipt, ID checked, or the goods leaving) to the pick_batch row, tagged description='Pickup photo'. Distinct from proof-of-pack photos (#1149), which share the pick_batch anchor but a different description. At least one pickup photo is a prerequisite for the pickup-confirm endpoint above. Multiple photos allowed. Refused with 409 once the batch is picked up or cancelled.

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>
Request Body schema: multipart/form-data
required
photo
required
string <binary>

Image file, up to 50 MB. JPEG/PNG/etc.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Flag a batch for office tracking-entry follow-up (idempotent)

Authorizations:
BearerAuth
path Parameters
batchID
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Locations

Warehouse location CRUD, lifecycle (retire/reactivate), and stock relocation. Locations are warehouse-wide — no manufacturer scoping. Relocate and move-all support an auto_create confirm pattern: an unknown destination code returns 422 with the code echoed; re-POST with the auto-create flag to create-on-scan and proceed.

List warehouse locations (#1396)

Paginated list of warehouse locations with per-bin item counts. Requires inventory.view. Locations are warehouse-wide shared infrastructure; any actor with the permission may list them. code resolves a scanned barcode / location code exactly (trimmed, case-insensitive).

Authorizations:
BearerAuth
query Parameters
zone
string
type
string

location_type filter (e.g. storage

status
string
Enum: "active" "retired"
code
string

Exact location-code lookup.

page
integer >= 1
per_page
integer [ 1 .. 100 ]

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Create a warehouse location (Phase 3c)

Inserts a new location in the default warehouse. Requires inventory.edit. Locations are warehouse-wide — not manufacturer-scoped. Returns 422 code_taken when the location_code collides with an existing row.

Authorizations:
BearerAuth
Request Body schema: application/json
required
location_code
required
string
location_type
required
string
Enum: "storage" "staging" "receiving" "shipping" "quarantine"
zone
string
aisle
string
rack
string
shelf
string
bin
string
capacity
integer

Responses

Request samples

Content type
application/json
{
  • "location_code": "string",
  • "location_type": "storage",
  • "zone": "string",
  • "aisle": "string",
  • "rack": "string",
  • "shelf": "string",
  • "bin": "string",
  • "capacity": 0
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a warehouse location (#1396)

Requires inventory.view.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a warehouse location (Phase 3c)

PATCH semantics — omitted fields preserve.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
location_code
string
location_type
string
Enum: "storage" "staging" "receiving" "shipping" "quarantine"
zone
string
aisle
string
rack
string
shelf
string
bin
string
capacity
integer

Responses

Request samples

Content type
application/json
{
  • "location_code": "string",
  • "location_type": "storage",
  • "zone": "string",
  • "aisle": "string",
  • "rack": "string",
  • "shelf": "string",
  • "bin": "string",
  • "capacity": 0
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List the stock sitting in a bin (#1396)

Contents of one location. Requires inventory.view. Supply-partner- scoped actors see only their own items in the bin — other supply partners' stock is filtered out, not 403'd, because the bin itself is shared infrastructure.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Retire a warehouse location (Phase 3c)

Soft-deletes the location. Refused if any stock remains (422 has_stock) or the location is a quarantine bin (422 quarantine_refused).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Reactivate a retired location (Phase 3c)

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Multi-item relocate from this location (Phase 3c)

Moves N items from this source bin to N destinations atomically. Each move targets a destination by code (resolved to id under the default warehouse). When auto_create_dests is omitted/false and any code doesn't resolve, the response is 422 unknown_dests with the codes echoed in unknown_dests — re-POST with auto_create_dests=true to create-on-scan and complete the move.

Empty moves, or moves with quantity <= 0 / empty dest_code, are silently dropped (matches the web's row-by-row form skip).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
auto_create_dests
boolean
Default: false
Array of objects

Responses

Request samples

Content type
application/json
{
  • "auto_create_dests": false,
  • "moves": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Empty a location into another (Phase 3c)

Moves everything in the source bin to dest_code. When the destination does not exist and auto_create=false, response is 422 unknown_dest with the code echoed in dest_code.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
dest_code
required
string
auto_create
boolean
Default: false

Responses

Request samples

Content type
application/json
{
  • "dest_code": "string",
  • "auto_create": false
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Bulk-create locations from a range spec (Phase 3d)

Generates the cartesian product of the supplied axes, applies the code_template, and inserts each row in one transaction. Existing codes are skipped (counted in result.skipped_existing); new rows are created. Hard cap: 5,000 rows per call → 422 too_large if the product exceeds it.

The web's HTML preview step is intentionally not exposed — n8n / scripts know what they're submitting (same precedent as receiving import).

Authorizations:
BearerAuth
Request Body schema: application/json
required
location_type
required
string
Enum: "storage" "staging" "receiving" "shipping" "quarantine"
code_template
string

Token template with {zone}, {aisle}, {rack}, {shelf}, {bin} placeholders. Empty defaults to "{zone}-{aisle}-{shelf}-{bin}".

capacity
integer
object

Responses

Request samples

Content type
application/json
{
  • "location_type": "storage",
  • "code_template": "string",
  • "capacity": 0,
  • "ranges": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Import locations from CSV (Phase 3d)

Imports a CSV file of locations, creating new rows and updating existing ones (matched by location_code). Per-row errors are surfaced in result.row_errors while other rows still commit (matches the web's tolerant import semantics).

Two body shapes are accepted:

  • multipart/form-data with a csv_file field
  • application/json with { "csv_text": "..." }

Both share the JSON group's 5 MB body cap. The web's HTML preview step is not exposed — preview-then-confirm is a UI affordance.

Authorizations:
BearerAuth
Request Body schema:
required
csv_file
required
string <binary>

Responses

Request samples

Content type
No sample

Response samples

Content type
application/json
{
  • "data": {
    }
}

Admin Users

User and role administration. Requires the system_admin role (matches the web's /admin/users and /admin/config gating). Consignor users are created via the invite flow (no password, manufacturer required); other roles use direct-create with a password. The consignor-create response includes the generated invite_url — equivalent to the web's invite-delivery page so callers on a log-mailer dev setup can deliver the link out-of-band.

List users (#1396)

Every user with role, supply-partner scope, MFA/PIN state, and last login. Requires the system_admin role (like every /admin endpoint). Unpaginated - the internal staff list is small and bounded.

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Create a user (standard or consignor)

Creates a user. Branches on the resolved role name:

  • consignor → invite flow. email and manufacturer_id are required; password is ignored. Returns user, invite_url, and email_sent.
  • standard (any non-consignor role) → direct-create. password is required and validated. Returns user.

Requires the system_admin role.

Authorizations:
BearerAuth
Request Body schema: application/json
required
username
required
string
display_name
required
string
role_id
required
string <uuid>
email
string

Required for consignor; optional otherwise.

password
string

Required for non-consignor roles.

manufacturer_id
string <uuid>

Required for consignor users.

Responses

Request samples

Content type
application/json
{
  • "username": "string",
  • "display_name": "string",
  • "role_id": "ac4e70c8-d5be-48af-93eb-760f58fc91a9",
  • "email": "string",
  • "password": "string",
  • "manufacturer_id": "cafecd41-5060-405f-a23d-b38c40f09e3f"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a user

PATCH semantics — every body field is optional. Omitted fields are unchanged. manufacturer_id is tri-state: omit = no change, explicit null = clear (refused on consignors), string = set.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
display_name
string
email
string
role_id
string <uuid>
manufacturer_id
string or null

string = set; null = clear (refused on consignors); omit = no change.

Responses

Request samples

Content type
application/json
{
  • "display_name": "string",
  • "email": "string",
  • "role_id": "ac4e70c8-d5be-48af-93eb-760f58fc91a9",
  • "manufacturer_id": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Revoke open invites and send a fresh one

Revokes any open invite for the user and creates a new one, emailing the link. Returns the link in the response body so log-mailer or test setups can still deliver it. Mailer failure does NOT fail the call; check email_sent.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Deactivate a user

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Activate a user

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Wipe a user's MFA enrollment + lockout

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Clear a user's MFA failure counter / lockout (without disenrolling)

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Set a new password for a user (admin)

Sets a new password immediately. Validates complexity, clears any account lockout, and stamps password_changed_at — which invalidates the target user's existing sessions (tokens issued before the change are rejected). Same system_admin gate as the other admin user endpoints.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
password
required
string

New password. Must pass complexity validation: min 8 chars, at least one uppercase, lowercase, digit, and special character, and not a common password.

Responses

Request samples

Content type
application/json
{
  • "password": "string"
}

Response samples

Content type
application/json
{
  • "error": {
    }
}

Update system configuration

PATCH semantics — every body field is optional. Omitting a field leaves it unchanged. receiving_stuck_threshold_hours is bounded to 1..168 (web silently drops out-of-range; the API returns 400 bad_request so callers can see their bug). Sequences cannot be moved backwards: 422 sequence_backwards.

Authorizations:
BearerAuth
Request Body schema: application/json
order_seq
integer <int64>

Set order_number_seq forward (cannot go backwards).

release_seq
integer <int64>

Set release_number_seq forward (cannot go backwards).

cycle_count_items_per_batch
integer >= 1
require_cycle_count_before_orders
boolean
document_retention_years
integer >= 1
receiving_stuck_threshold_hours
integer [ 1 .. 168 ]
price_visibility_enabled
boolean
org_name
string

Operating company / sender display name. Trimmed on write; an empty string resets to the "ConsignTrak" default on read.

Responses

Request samples

Content type
application/json
{
  • "order_seq": 0,
  • "release_seq": 0,
  • "cycle_count_items_per_batch": 1,
  • "require_cycle_count_before_orders": true,
  • "document_retention_years": 1,
  • "receiving_stuck_threshold_hours": 1,
  • "price_visibility_enabled": true,
  • "org_name": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Webhooks

Outbound HTTP webhook subscriptions for the shipment-event surface (issue #868). The push counterpart to GET /api/v1/shipments/events: each registered subscription receives an HMAC-signed POST for every matching event. Tenant-scoped — manufacturer-scoped API keys can only manage subscriptions for their own supply partner.

The plaintext signing_secret is returned ONCE on POST and on rotate-secret; subsequent reads omit it. Receivers verify each delivery's X-ConsignTrak-Signature header (Stripe-style t=<unix>,v1=<hex> HMAC-SHA256 over <unix>.<body>) before trusting the payload, and SHOULD reject deliveries whose timestamp drifts more than 5 minutes from wall clock to defend against replay.

Delivery semantics: 2xx = delivered; 429 / 5xx / network = retry with exponential backoff (1s → 128s, jittered) up to max_attempts; other 4xx = permanent failure (no retry — receiver rejected the payload). After max_attempts of transient failure the delivery is dead-lettered.

Register a webhook subscription

Creates a new subscription. The plaintext signing_secret is returned on the response — exactly once, here. Store it on the receiver immediately; ConsignTrak does not retain a way to re-issue it for the same secret (use rotate-secret to mint a new one).

Tenant scope: manufacturer-scoped API keys are forced to their own scope. An explicit manufacturer_id matching the key's scope is allowed; a different value returns 403.

Requires orders.view permission.

Authorizations:
BearerAuth
Request Body schema: application/json
required
url
required
string <uri>

HTTPS receiver URL. http:// is permitted in dev but production should always use https.

event_types
required
Array of strings non-empty

Event types this subscription should receive. Must be non-empty, and every value must be a known event type — an unknown or typo'd value is rejected with 422 unknown_event_type (it would otherwise be accepted and silently never deliver). Enumerate the valid values with GET /api/v1/webhooks/event-types. Current catalog: order.block_resolved.v1, order.blocked.v1, order.direct_cancelled.v1, order.direct_notified.v1, order.direct_released.v1, order.shipped.v1, period.closed.v1.

manufacturer_id
string <uuid>

Optional. Admin keys may set this to scope the subscription to one supply partner. Manufacturer-scoped keys: omit (auto-filled from key scope) or pass the same value as the key's scope (mismatched values return 403).

description
string

Free-form note for operators ("Repfabric n8n connector — prod").

max_attempts
integer [ 1 .. 24 ]
Default: 8

Per-subscription override of the global max_attempts. Reject > 24.

Responses

Request samples

Content type
application/json
{
  • "event_types": [
    ],
  • "manufacturer_id": "cafecd41-5060-405f-a23d-b38c40f09e3f",
  • "description": "string",
  • "max_attempts": 8
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List webhook subscriptions

Returns subscriptions visible to the caller. Manufacturer-scoped keys see only their own tenant. Admin keys see all subscriptions including admin-wide ones (NULL manufacturer_id). Inactive subscriptions are excluded by default; pass ?include_inactive=true to include them.

signing_secret is NEVER included on this surface — only on POST and rotate-secret.

Requires orders.view permission.

Authorizations:
BearerAuth
query Parameters
include_inactive
boolean
Default: false

Set to true to include deactivated subscriptions.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

List subscribable event types

Discovery endpoint: returns the catalog of event types a webhook subscription may include in event_types, sorted lexicographically. Integrators (and n8n workflows) should enumerate types from here rather than transcribing them from docs — values outside this catalog are rejected with 422 on subscription create.

Requires orders.view permission.

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Fetch one webhook subscription

Requires orders.view. Manufacturer-scoped keys get 404 (not 403) on cross-tenant ids — existence is not leaked.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Deactivate a webhook subscription

Soft-deletes the subscription (sets is_active=false). Past deliveries' FK references stay valid and the audit trail is intact. Hard delete is intentionally not exposed.

Requires orders.view. Manufacturer-scoped keys get 404 (not 403) on cross-tenant ids.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Rotate the signing secret for a subscription

Generates a fresh 32-byte signing secret, replacing the previous one. Increments signing_secret_version. Returns the plaintext once — receivers must update their stored secret BEFORE the next delivery or signature verification will fail.

Today the dispatcher always signs with the latest secret; future schemes may support overlapping windows keyed by signing_secret_version.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Account Security

Self-service MFA + trusted-device management for the API key holder's own account. The endpoints never accept a target user; identity is taken from auth.UserFromContext(...).ID. Mirrors the web /account/security page. Step-up: regenerate-recovery and disable-MFA require a body {otp} when the caller has MFA enrolled (the same step-up the web page enforces).

Regenerate the caller's MFA recovery codes

Step-up: requires {otp} in the body when MFA is enrolled. Returns 10 freshly-generated recovery codes once. The response is Cache-Control: private, no-store so the codes never persist in shared caches.

Authorizations:
BearerAuth
Request Body schema: application/json
otp
string

Required when caller has MFA enrolled.

Responses

Request samples

Content type
application/json
{
  • "otp": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Disable the caller's MFA enrollment

Step-up: requires {otp} in the body when MFA is enrolled. Wipes TOTP secret, recovery codes, and trusted devices.

Authorizations:
BearerAuth
Request Body schema: application/json
otp
string

Responses

Request samples

Content type
application/json
{
  • "otp": "string"
}

Response samples

Content type
application/json
{
  • "error": {
    }
}

Revoke one of the caller's trusted MFA devices

Removes a remembered-device row. The device must belong to the caller; passing a device ID owned by another user yields 404 (deliberate — RevokeMFADevice scopes by user_id).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Reporting

Operational reports for office staff. Read-only, gated by history.view. Consignor-scoped users get 404 (these are warehouse-wide activity views, not tenant-scoped).

End-of-day shipment report

Returns the orders that physically shipped from the warehouse on the given date, grouped by supply partner. Excludes 100% direct-ship orders; on split orders, returns only the warehouse-fulfilled lines.

date is a civil day in the warehouse's local timezone (for example America/New_York). Defaults to today. Invalid date falls back to today (does NOT 400).

Permission: history.view. Consignor-scoped or manufacturer- scoped users get 404 — this is a warehouse-wide activity view, not a tenant-scoped one.

Authorizations:
BearerAuth
query Parameters
date
string <date>
Example: date=2026-05-12

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Supply Partners

List supply partners

Returns all active supply partners for dropdown/lookup use. Not paginated (a warehouse has at most a few dozen supply partners). Supply-partner-scoped users see only their own supply partner. Requires manufacturers.view permission.

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Create a supply partner

Creates a new supply partner record. Requires manufacturers.edit permission AND an unscoped key (manufacturer-scoped users cannot mint additional supply partners). commission_rate is a decimal fraction (0.10 = 10%); the web form takes a percentage but the API takes the storage form so n8n round-trips don't have to multiply. type_code defaults to consigned when omitted.

Authorizations:
BearerAuth
Request Body schema: application/json
required
code
required
string

Legacy CAS manufacturer code (e.g. "5" for AMEC). Unique.

name
required
string
short_name
string
type_code
string
Default: "consigned"
rep_code
string
commission_rate
number or null <float>

Decimal fraction (0.10 = 10%)

selling_agency
string
shipping_agency
string
freight_billing_info
string
notes
string

Responses

Request samples

Content type
application/json
{
  • "code": "string",
  • "name": "string",
  • "short_name": "string",
  • "type_code": "consigned",
  • "rep_code": "string",
  • "commission_rate": 0.1,
  • "selling_agency": "string",
  • "shipping_agency": "string",
  • "freight_billing_info": "string",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": { }
}

Update a supply partner

Updates an existing supply partner. Requires manufacturers.edit permission. Manufacturer-scoped keys can only update their own supply partner. Omitting type_code or statement_cadence preserves the existing value (matches the web "mobile form omits the field" fallback).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
name
required
string
short_name
string
type_code
string
rep_code
string
commission_rate
number or null <float>

Decimal fraction (0.10 = 10%)

selling_agency
string
shipping_agency
string
freight_billing_info
string
notes
string
statement_cadence
string
Enum: "monthly" "quarterly"

When omitted, the existing cadence is preserved.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "short_name": "string",
  • "type_code": "string",
  • "rep_code": "string",
  • "commission_rate": 0.1,
  • "selling_agency": "string",
  • "shipping_agency": "string",
  • "freight_billing_info": "string",
  • "notes": "string",
  • "statement_cadence": "monthly"
}

Response samples

Content type
application/json
{
  • "data": { }
}

Item Prices

List supply-partner item prices

Lists prices for the given supply partner. Optional item_id filter narrows to a single item; optional active_at (RFC3339) returns only rows whose effective window contains the timestamp. Without active_at, the full history (all open + closed rows) is returned. Requires manufacturers.view. Manufacturer-scoped keys can only target their own supply partner.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
query Parameters
item_id
string <uuid>
active_at
string <date-time>

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Get a single price row

Fetches a specific price row by ID. Requires manufacturers.view. Returns 404 when the row doesn't exist or belongs to a different supply partner (the 404-on-cross-tenant pattern matches the rest of the API).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
price_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

End-date a price row

Sets effective_to = now() on a still-open price row. Never hard-deletes — historical rows are part of the audit surface. Requires manufacturers.edit.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
price_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Update notes on a price row

Updates ONLY the notes field on a price row. The unit_price, effective_from, and effective_to columns remain immutable through this endpoint — to change those, use DELETE (end-date) and PUT (insert new). Issue #954. Requires manufacturers.edit.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
price_id
required
string <uuid>
Request Body schema: application/json
required
notes
string or null

Responses

Request samples

Content type
application/json
{
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Cancel a scheduled (future-dated) price

Hard-deletes a future-dated (still-open) price row AND restores the prior open row's effective_to to NULL in one transaction. The restore is required because PUT end-dates the prior row at the new row's effective_from; cancelling the new row alone would leave the item with no active price when the cancelled date passes. Issue #954. Requires manufacturers.edit. Distinct from DELETE to preserve existing end-date semantics for already-shipped clients.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
price_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Set the active price for an item

Idempotent upsert keyed by item. End-dates any prior open row at the new row's effective_from and inserts a new row. Pass effective_from to set a future- or past-effective price; omit to use server now. Requires manufacturers.edit. The trigger rejects mismatched manufacturer_id/item_id pairs as 404 ("item not in your scope").

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>
item_id
required
string <uuid>
Request Body schema: application/json
required
unit_price
required
number <float>

USD; must be >= 0.

effective_from
string or null <date-time>

RFC3339 timestamp; defaults to server now when omitted.

notes
string or null

Responses

Request samples

Content type
application/json
{
  • "unit_price": 0.1,
  • "effective_from": "2019-08-24T14:15:22Z",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Documents

Download a document

Streams the document bytes. Requires documents.view permission AND the parent entity's *.view permission (e.g., orders.view to download an order attachment). Manufacturer-scoped keys can only download documents whose parent entity belongs to their supply partner. Always returns Content-Disposition: attachment — the inline preview behavior is HTML-only.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Delete a document

Soft-deletes a document. Requires documents.delete permission AND the parent entity's *.view permission. Manufacturer-scoped keys can only delete documents whose parent entity belongs to their supply partner.

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Upload a document

Uploads a document (PDF, image, Excel, CSV, Word, TXT) as an attachment to a parent entity. Accepts multipart/form-data with a file part. Supports 50 MB body (the JSON /api/v1/ cap is 5 MB — this endpoint has a dedicated larger cap). Requires documents.upload permission.

Authorizations:
BearerAuth
path Parameters
entityType
required
string
Enum: "order" "item" "manufacturer" "customer" "receiving_line" "mispick_exception" "quality_exception" "short_pick_exception"
entityID
required
string <uuid>
Request Body schema: multipart/form-data
required
file
required
string <binary>
description
string
tags
Array of strings

Optional labels stored on the document. Repeat the field to send multiple. The value manufacturer-packing-list marks an order's document as the supply partner's own packing list, which is then auto-surfaced for printing on the warehouse pack screen (#1203).

Responses

Response samples

Content type
application/json
{
  • "data": { }
}

Events

List unacked outbound events

Cursor-style poll for the outbound event stream. Used by n8n and any other pull-based integration consumer. Returns unacked events ordered by (occurred_at ASC, id ASC) for deterministic cursor advance. Requires events.read permission.

Authorizations:
BearerAuth
query Parameters
limit
integer [ 1 .. 500 ]
Default: 100

Page size; defaults to 100, clamped to 500.

since
string <date-time>

RFC3339 occurred_at floor (inclusive).

types
string
Example: types=order.shipped.v1,order.released.v1

CSV of event_type values to include.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Acknowledge an event

Marks the given event as delivered. Idempotent — acking a previously-acked event succeeds without changing the original published_at timestamp. Requires events.read permission (acking requires the same scope as reading).

Authorizations:
BearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Carrier Credentials

Read carrier credentials (no secrets)

Returns the read-side projection of the credential row for the given supply partner + carrier. Never includes secrets. Requires system_admin role; additionally, an API key scoped to supply partner A cannot read supply partner B even with the role (forbidden_cross_tenant).

Authorizations:
BearerAuth
path Parameters
mfgID
required
string <uuid>

Supply partner (manufacturer) ID.

carrier
required
string
Enum: "fedex" "ups"

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Create or update carrier credentials

Upsert. The carrier path segment selects the variant schema for the request body (FedEx vs UPS). Returns the read-side projection on success; secrets are not echoed back. To obtain the just-set secret, use the rotate endpoint instead (the reveal-once response carries it).

Role: system_admin. Per-tenant scope check applies to API keys.

Authorizations:
BearerAuth
path Parameters
mfgID
required
string <uuid>

Supply partner (manufacturer) ID.

carrier
required
string
Enum: "fedex" "ups"
Request Body schema: application/json
required
hmac_secret
string

FedEx variant. Shared signing secret FedEx publishes for the AIV webhook subscription. Required when {carrier}=fedex.

oauth_client_id
string

UPS variant. OAuth client ID. Required when {carrier}=ups.

oauth_client_secret
string

UPS variant. OAuth client secret. Required when {carrier}=ups.

ups_shipper_number
string

UPS variant. UPS Shipper Number that scopes Tracking-by-reference queries. Required when {carrier}=ups.

poll_interval_seconds
integer [ 30 .. 3600 ]

Optional poll-cadence override (seconds). Defaults to 60. Only consulted by the UPS poller; ignored on FedEx rows.

Responses

Request samples

Content type
application/json
{
  • "hmac_secret": "string",
  • "oauth_client_id": "string",
  • "oauth_client_secret": "string",
  • "ups_shipper_number": "string",
  • "poll_interval_seconds": 30
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete carrier credentials

Removes the credential row. Subsequent FedEx webhook deliveries for this tenant return 410 (Phase 3); UPS poller skips this tenant on the next cycle (Phase 4). Not idempotent — 404 if no row exists.

Authorizations:
BearerAuth
path Parameters
mfgID
required
string <uuid>

Supply partner (manufacturer) ID.

carrier
required
string
Enum: "fedex" "ups"

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Rotate carrier credentials (returns just-set secret once)

Rotates the credential secret to the value supplied in the body. The prior secret stops working immediately. The response includes the just-set secret in hmac_secret_reveal_once (FedEx) or oauth_client_secret_reveal_once (UPS) — this is the only response that ever carries the plaintext secret. Subsequent reads via GET never include it; if lost, rotate again.

Returns 404 when no existing row to rotate (use POST to create).

Authorizations:
BearerAuth
path Parameters
mfgID
required
string <uuid>
carrier
required
string
Enum: "fedex" "ups"
Request Body schema: application/json
required
hmac_secret
string

FedEx variant. Shared signing secret FedEx publishes for the AIV webhook subscription. Required when {carrier}=fedex.

oauth_client_id
string

UPS variant. OAuth client ID. Required when {carrier}=ups.

oauth_client_secret
string

UPS variant. OAuth client secret. Required when {carrier}=ups.

ups_shipper_number
string

UPS variant. UPS Shipper Number that scopes Tracking-by-reference queries. Required when {carrier}=ups.

poll_interval_seconds
integer [ 30 .. 3600 ]

Optional poll-cadence override (seconds). Defaults to 60. Only consulted by the UPS poller; ignored on FedEx rows.

Responses

Request samples

Content type
application/json
{
  • "hmac_secret": "string",
  • "oauth_client_id": "string",
  • "oauth_client_secret": "string",
  • "ups_shipper_number": "string",
  • "poll_interval_seconds": 30
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Carrier Webhooks

FedEx AIV shipment-event receiver

Inbound push-webhook receiver for FedEx Shipment Visibility AIV (issue #1143 Phase 3). FedEx posts shipment-event envelopes (label_creation, in_transit, etc.) with an Fdx-Signature HMAC header; ConsignTrak verifies the signature in constant time, dedups against carrier_event_log.(carrier, carrier_event_id), normalizes the payload, and runs `shipimport.Stage + MatchMulti

  • Committo update the matching pick batch'stracking_number, tracking_carrier, and tracking_captured_at`.

Only LABEL_CREATION events drive tracking writes. Other event types are signature-verified, ignored, and acknowledged with 200 {"status":"ignored"} so FedEx doesn't retry them.

Idempotency: replays of the same eventId short-circuit on the ON CONFLICT DO NOTHING path and return 200 {"status":"replayed"} without re-running Stage/Match/Commit. Bad-signature deliveries write a carrier_transport_log row with outcome signature_rejected and return 401 without any other DB write. Tombstoned (deleted) tenant tokens return 410 Gone so FedEx stops retrying.

Authorizations:
FdxSignatureHMAC
path Parameters
tenantToken
required
string

Per-credential opaque URL token generated by ConsignTrak on FedEx-variant carrier_credentials Upsert/Rotate. Selects which tenant's HMAC secret to verify the body against. Not a secret — HMAC verification is the actual auth surface — but unguessable.

Request Body schema: application/json
required
eventId
required
string

Carrier-supplied unique event id; used as the idempotency key on carrier_event_log.

eventType
required
string

AIV event type. LABEL_CREATION drives a tracking write; others are signature-verified and acknowledged but not normalized.

eventTimestamp
string <date-time>
accountNumber
string

FedEx account number; informational. The URL tenantToken is the actual tenant discriminator.

trackingNumber
required
string
shipDate
string <date>
Array of objects
object

Responses

Request samples

Content type
application/json
{
  • "eventId": "string",
  • "eventType": "string",
  • "eventTimestamp": "2019-08-24T14:15:22Z",
  • "accountNumber": "string",
  • "trackingNumber": "string",
  • "shipDate": "2019-08-24",
  • "customerReferences": [
    ],
  • "recipient": {
    }
}

Response samples

Content type
application/json
{
  • "status": "applied",
  • "outcome": "applied",
  • "eventId": "string",
  • "eventType": "string",
  • "summary": {
    }
}

Admin

Manually trigger one carrier poll cycle for a supply partner

Issue #1143 Phase 4. Runs one UPS Tracking-by-reference cycle for the named supply partner: iterates the tenant's open orders, queries UPS for each, and applies any matching tracking numbers via the same Stage + MatchMulti + Commit path the background scheduler uses. API-only — no UI twin. The production code path is the server's background scheduler (60s default cadence); this endpoint exists for ops debugging and the e2e harness.

system_admin only. FedEx variant returns 422 (push lane has no poll cycle). Returns 503 when carrier ingestion is not configured.

Authorizations:
BearerAuth
path Parameters
mfgID
required
string <uuid>

Manufacturer (supply partner) ID to poll for.

carrier
required
string
Enum: "ups" "fedex"

Carrier identifier. Currently only ups is implemented (FedEx is push-based — its production code path is the AIV webhook, not a poller).

Responses

Response samples

Content type
application/json
{
  • "status": "ok",
  • "manufacturer_id": "cafecd41-5060-405f-a23d-b38c40f09e3f",
  • "carrier": "ups"
}

Manually trigger one carrier-reconciliation cycle for a supply partner

Issue #1143 Phase 5. Runs one carrier-reconciliation cycle for the named supply partner: asks the carrier-side Track-by-reference endpoint for hits on every still-open order, cross-checks each hit against carrier_event_log, and flags any unknown hits as a discrepancy (a shipment_imports row with discrepancy_source='reconciliation' that surfaces on the existing pending-tracking queue page). API-only — no UI twin. The production code path is the daily cron in server (fires at the configured reconcile hour, UTC, default 02). This endpoint exists for ops debugging and the e2e harness.

system_admin only. Returns 503 when carrier ingestion is not configured. Returns 404 for unknown carriers.

Authorizations:
BearerAuth
path Parameters
mfgID
required
string <uuid>

Supply-partner ID to reconcile.

carrier
required
string
Enum: "ups" "fedex"

Carrier identifier. Both fedex and ups are accepted — the reconciliation lane is symmetric across carriers (both query the carrier-side Track-by-reference endpoint per still-open order). Unknown carriers return 404.

Responses

Response samples

Content type
application/json
{
  • "status": "ok",
  • "manufacturer_id": "cafecd41-5060-405f-a23d-b38c40f09e3f",
  • "carrier": "ups",
  • "records_fetched": 0,
  • "discrepancies_found": 0
}

Carrier ingestion health and reconciliation figures (all supply partners)

Issue #1143 Phase 6. JSON twin of the read-only carrier-health dashboard at /admin/carriers/health. Returns per-(supply partner, carrier) ingestion figures plus cross-carrier top-line aggregates and the recent-events / reconciliation-discrepancy ledgers.

Surfaces the AC's five failure-visibility figures per carrier: last-event-received timestamp, events received in the last hour, FedEx signature-rejection count, UPS poll-error count, and reconciliation discrepancies (last 7 days).

system_admin only. This endpoint reports across every tenant, so it is restricted to system_admin; a manufacturer-scoped API key cannot reach it. Read-only. Returns 503 when carrier ingestion is not configured (encryption key missing).

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "total_events_last_hour": 0,
  • "total_signature_rejections_last_hour": 0,
  • "total_poll_errors_last_hour": 0,
  • "total_reconciliation_discrepancies_last_7d": 0,
  • "total_pending_events": 0,
  • "carriers": [
    ],
  • "recent_events": [
    ],
  • "reconciliation_discrepancies": [
    ]
}