{"openapi":"3.0.0","paths":{"/api/v1/webhooks/events":{"get":{"operationId":"OutboundWebhooksController_listEvents","summary":"List the event names available to subscribe to","parameters":[],"responses":{"200":{"description":""}},"tags":["Outbound Webhooks"]}},"/api/v1/webhooks":{"get":{"operationId":"OutboundWebhooksController_list","summary":"List webhook endpoints for the caller org","parameters":[],"responses":{"200":{"description":""}},"tags":["Outbound Webhooks"]},"post":{"operationId":"OutboundWebhooksController_create","summary":"Create a new webhook endpoint (returns secret — store it now)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEndpointDto"}}}},"responses":{"201":{"description":""}},"tags":["Outbound Webhooks"]}},"/api/v1/webhooks/{id}":{"patch":{"operationId":"OutboundWebhooksController_update","summary":"Update a webhook endpoint","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEndpointDto"}}}},"responses":{"200":{"description":""}},"tags":["Outbound Webhooks"]},"delete":{"operationId":"OutboundWebhooksController_delete","summary":"Delete a webhook endpoint (cascades to its deliveries)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["Outbound Webhooks"]}},"/api/v1/webhooks/{id}/rotate-secret":{"post":{"operationId":"OutboundWebhooksController_rotateSecret","summary":"Generate a new signing secret (invalidates the old one immediately)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Outbound Webhooks"]}},"/api/v1/webhooks/{id}/deliveries":{"get":{"operationId":"OutboundWebhooksController_listDeliveries","summary":"List recent deliveries for an endpoint (newest first)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Outbound Webhooks"]}},"/api/v1/webhooks/deliveries/{deliveryId}/replay":{"post":{"operationId":"OutboundWebhooksController_replay","summary":"Re-queue a failed delivery for another attempt","parameters":[{"name":"deliveryId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Outbound Webhooks"]}},"/api/v1/purchase-orders":{"post":{"operationId":"PurchaseOrdersController_create","summary":"Create a purchase order","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePurchaseOrderDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]},"get":{"operationId":"PurchaseOrdersController_findAll","summary":"List purchase orders (filter by status, channel, search)","parameters":[{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"hasInvoice","required":true,"in":"query","schema":{"type":"string"}},{"name":"hasAttachment","required":false,"in":"query","description":"true → only POs carrying at least one document.","schema":{"type":"string"}},{"name":"attachmentType","required":false,"in":"query","description":"Narrow to POs carrying a document of this kind. \"image\" finds hand-captured receipts — inbound vendor mail never attaches images, so every image was uploaded deliberately. Implies hasAttachment=true.","schema":{"enum":["image","pdf"],"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}},{"name":"take","required":true,"in":"query","schema":{"type":"string"}},{"name":"skip","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortBy","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortOrder","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/trash":{"get":{"operationId":"PurchaseOrdersController_findDeleted","summary":"List soft-deleted (trashed) purchase orders","parameters":[{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/bulk-from-requirements":{"post":{"operationId":"PurchaseOrdersController_bulkFromRequirements","summary":"Bulk-create purchase orders from procurement requirements","parameters":[],"responses":{"201":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}":{"get":{"operationId":"PurchaseOrdersController_findOne","summary":"Get a purchase order by id","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]},"put":{"operationId":"PurchaseOrdersController_update","summary":"Update a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePurchaseOrderDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]},"delete":{"operationId":"PurchaseOrdersController_delete","summary":"Soft-delete a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/restore":{"post":{"operationId":"PurchaseOrdersController_restore","summary":"Restore a soft-deleted purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/submit":{"post":{"operationId":"PurchaseOrdersController_submit","summary":"Submit a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/cancel":{"post":{"operationId":"PurchaseOrdersController_cancel","summary":"Cancel a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/send-email":{"post":{"operationId":"PurchaseOrdersController_sendEmail","summary":"Email a purchase order to the supplier","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/send-follow-up":{"post":{"operationId":"PurchaseOrdersController_sendFollowUp","summary":"Send a follow-up message on a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/pdf":{"get":{"operationId":"PurchaseOrdersController_downloadPdf","summary":"Download a purchase order as PDF","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/lines":{"post":{"operationId":"PurchaseOrdersController_addLine","summary":"Add a line item to a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddLineDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/lines/{lineId}":{"put":{"operationId":"PurchaseOrdersController_updateLine","summary":"Update a purchase order line item","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"lineId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLineDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]},"delete":{"operationId":"PurchaseOrdersController_deleteLine","summary":"Delete a purchase order line item","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"lineId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/lines/{lineId}/receive":{"post":{"operationId":"PurchaseOrdersController_receiveLine","summary":"Receive quantity against a purchase order line","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"lineId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiveLineDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/lines/{lineId}/link-reservations":{"post":{"operationId":"PurchaseOrdersController_linkReservations","summary":"Link order reservations to a purchase order line","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"lineId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkReservationsDto"}}}},"responses":{"201":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/reservations":{"get":{"operationId":"PurchaseOrdersController_getReservations","summary":"List reservations linked to a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/linkable-reservations":{"get":{"operationId":"PurchaseOrdersController_getLinkableReservations","summary":"List reservations that can be linked to a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/messages":{"get":{"operationId":"PurchaseOrdersController_getMessages","summary":"List communication messages for a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/tracking":{"post":{"operationId":"PurchaseOrdersController_addTracking","summary":"Add tracking information to a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddTrackingDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/attachments":{"get":{"operationId":"PurchaseOrdersController_getAttachments","summary":"List attachments on a purchase order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["purchase-orders"]},"post":{"operationId":"PurchaseOrdersController_createAttachment","summary":"Record a purchase order attachment after upload","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/attachments/upload-url":{"post":{"operationId":"PurchaseOrdersController_getAttachmentUploadUrl","summary":"Get a presigned URL to upload a purchase order attachment","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"filename","required":true,"in":"query","schema":{"type":"string"}},{"name":"contentType","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/purchase-orders/{id}/attachments/{attachmentId}":{"delete":{"operationId":"PurchaseOrdersController_deleteAttachment","summary":"Delete a purchase order attachment","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"attachmentId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["purchase-orders"]}},"/api/v1/invoices":{"post":{"operationId":"InvoicesController_create","summary":"Create a vendor invoice","description":"Standard invoices (invoiceType INVOICE) require purchaseOrderId; credit/debit memos may reference a related invoice instead. Subtotal and total are computed from the lines and charges when omitted. Initial status depends on the org invoice workflow: OUTSTANDING (simple) or DRAFT (advanced), or PAID when markAsPaid is set.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoiceDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]},"get":{"operationId":"InvoicesController_findAll","summary":"List invoices (filter by purchase order, status, type, search)","description":"status and invoiceType accept comma-separated lists; search matches invoice number, PO number, and vendor name.","parameters":[{"name":"purchaseOrderId","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"invoiceType","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortBy","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortOrder","required":true,"in":"query","schema":{"type":"string"}},{"name":"take","required":true,"in":"query","schema":{"type":"string"}},{"name":"skip","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["invoices"]}},"/api/v1/invoices/stats":{"get":{"operationId":"InvoicesController_getStats","summary":"Get invoice summary statistics (counts plus outstanding, credit-memo, and debit-memo totals)","parameters":[],"responses":{"200":{"description":""}},"tags":["invoices"]}},"/api/v1/invoices/aging":{"get":{"operationId":"InvoicesController_getAging","summary":"Get an accounts-payable aging report of unpaid invoices bucketed by days past due","parameters":[],"responses":{"200":{"description":""}},"tags":["invoices"]}},"/api/v1/invoices/vendor-statement":{"get":{"operationId":"InvoicesController_getVendorStatement","summary":"Get a vendor statement for a supplier channel","description":"Lists the vendor’s invoices in the optional date range with invoice, credit, debit, paid, and outstanding totals (net = invoices + debits − credits).","parameters":[{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}},{"name":"dateFrom","required":true,"in":"query","schema":{"type":"string"}},{"name":"dateTo","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["invoices"]}},"/api/v1/invoices/trash":{"get":{"operationId":"InvoicesController_findDeleted","summary":"List soft-deleted (trashed) invoices","parameters":[{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["invoices"]}},"/api/v1/invoices/{id}":{"get":{"operationId":"InvoicesController_findOne","summary":"Get an invoice by id","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]},"put":{"operationId":"InvoicesController_update","summary":"Update an invoice","description":"Only DRAFT invoices can be updated.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInvoiceDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]},"delete":{"operationId":"InvoicesController_delete","summary":"Soft-delete an invoice","description":"Only DRAFT invoices can be deleted.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["invoices"]}},"/api/v1/invoices/{id}/restore":{"post":{"operationId":"InvoicesController_restore","summary":"Restore a soft-deleted invoice","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]}},"/api/v1/invoices/{id}/lines":{"post":{"operationId":"InvoicesController_addLine","summary":"Add a line item to an invoice","description":"Only on DRAFT invoices. A purchaseOrderLineId must belong to the invoice’s purchase order; when matched, quantity and cost variances against the PO line are calculated.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddInvoiceLineDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]}},"/api/v1/invoices/{id}/lines/{lineId}":{"put":{"operationId":"InvoicesController_updateLine","summary":"Update an invoice line item","description":"Only on DRAFT invoices; the line total and any PO-line variances are recalculated.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"lineId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInvoiceLineDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]},"delete":{"operationId":"InvoicesController_deleteLine","summary":"Delete an invoice line item","description":"Only on DRAFT invoices.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"lineId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["invoices"]}},"/api/v1/invoices/{id}/lines/{lineId}/match":{"post":{"operationId":"InvoicesController_matchLine","summary":"Match an invoice line to a purchase order line","description":"The PO line must belong to the invoice’s purchase order. Recalculates quantity variance (invoiced − received) and cost variance ((invoice cost − PO cost) × invoiced quantity).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"lineId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchLineDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]}},"/api/v1/invoices/{id}/auto-match":{"post":{"operationId":"InvoicesController_autoMatch","summary":"Auto-match unmatched invoice lines to purchase order lines by SKU","description":"Matches each unmatched line whose vendorSku equals a PO line’s supplier channel SKU or variant SKU, calculating variances. Returns the number of lines matched.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["invoices"]}},"/api/v1/invoices/{id}/submit":{"post":{"operationId":"InvoicesController_submit","summary":"Submit a DRAFT invoice for approval","description":"Moves the invoice from DRAFT to PENDING.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]}},"/api/v1/invoices/{id}/approve":{"post":{"operationId":"InvoicesController_approve","summary":"Approve an invoice","description":"Allowed from DRAFT, PENDING, or DISPUTED status.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]}},"/api/v1/invoices/{id}/dispute":{"post":{"operationId":"InvoicesController_dispute","summary":"Dispute an invoice","description":"Allowed from DRAFT, PENDING, or OUTSTANDING status; the optional reason is appended to the invoice notes.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]}},"/api/v1/invoices/{id}/reopen":{"post":{"operationId":"InvoicesController_reopen","summary":"Reopen a disputed invoice for editing","description":"Returns a DISPUTED invoice to OUTSTANDING (simple workflow) or DRAFT (advanced workflow).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]}},"/api/v1/invoices/{id}/reconcile":{"post":{"operationId":"InvoicesController_reconcile","summary":"Mark an invoice reconciled against the vendor’s paperwork","description":"Reconciliation is an overlay separate from invoice status; allowed on OUTSTANDING, APPROVED, or PAID invoices. When the org’s QuickBooks connection posts bills on reconcile, this releases the push.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]}},"/api/v1/invoices/{id}/unreconcile":{"post":{"operationId":"InvoicesController_unreconcile","summary":"Clear the reconciled mark on an invoice","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]}},"/api/v1/invoices/{id}/mark-paid":{"post":{"operationId":"InvoicesController_markPaid","summary":"Mark an invoice as paid","description":"Only APPROVED or OUTSTANDING invoices can be marked paid; records payment date, reference, and method.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkPaidDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["invoices"]}},"/api/v1/purchase-orders/{poId}/landed-costs":{"post":{"operationId":"PurchaseOrderLandedCostsController_create","summary":"Add a landed cost to a purchase order","description":"Allocation method defaults to BY_VALUE; an invoiceId must belong to the same purchase order.","parameters":[{"name":"poId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLandedCostDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["landed-costs"]},"get":{"operationId":"PurchaseOrderLandedCostsController_findAll","summary":"List landed costs on a purchase order","parameters":[{"name":"poId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["landed-costs"]}},"/api/v1/purchase-orders/{poId}/landed-costs/summary":{"get":{"operationId":"PurchaseOrderLandedCostsController_getCostSummary","summary":"Get a landed-cost summary for a purchase order","description":"Per-line and total breakdown of base (received) cost, allocated landed costs, and invoice variances, with per-unit costs.","parameters":[{"name":"poId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["landed-costs"]}},"/api/v1/landed-costs/{id}":{"get":{"operationId":"LandedCostsController_findOne","summary":"Get a landed cost by id","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["landed-costs"]},"put":{"operationId":"LandedCostsController_update","summary":"Update a landed cost","description":"Changing the amount or allocation method re-runs allocation unless the method is MANUAL.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLandedCostDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["landed-costs"]},"delete":{"operationId":"LandedCostsController_delete","summary":"Delete a landed cost and its allocations","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["landed-costs"]}},"/api/v1/landed-costs/{id}/allocate":{"post":{"operationId":"LandedCostsController_allocate","summary":"Allocate a landed cost across purchase order lines","description":"Replaces existing allocations, apportioning the amount by received line value (BY_VALUE), received quantity (BY_QUANTITY), or received weight (BY_WEIGHT). MANUAL landed costs must use the allocations endpoint instead.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["landed-costs"]}},"/api/v1/landed-costs/{id}/allocations":{"put":{"operationId":"LandedCostsController_setAllocations","summary":"Set manual per-line allocations for a landed cost","description":"Replaces existing allocations and sets the allocation method to MANUAL; amounts must sum to the landed cost amount (within $0.01).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetManualAllocationsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["landed-costs"]}},"/api/v1/orders":{"get":{"operationId":"OrdersController_findAll","summary":"List orders (filter by channel, status, date range, search)","parameters":[{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"startDate","required":true,"in":"query","schema":{"type":"string"}},{"name":"endDate","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortBy","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortOrder","required":true,"in":"query","schema":{"type":"string"}},{"name":"blockedOnBuild","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/trash":{"get":{"operationId":"OrdersController_findDeleted","summary":"List soft-deleted (trashed) orders","parameters":[{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/stats":{"get":{"operationId":"OrdersController_getStats","summary":"Get order statistics (optionally per channel)","parameters":[{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/pending-reservations":{"get":{"operationId":"OrdersController_getPendingReservations","summary":"List pending reservations","parameters":[{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["orders"]}},"/api/v1/orders/{id}":{"get":{"operationId":"OrdersController_findOne","summary":"Get an order by id","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/history":{"get":{"operationId":"OrdersController_getStatusHistory","summary":"Get an order's status history","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/reservations":{"get":{"operationId":"OrdersController_getOrderReservations","summary":"List reservations for an order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/reservations/status":{"get":{"operationId":"OrdersController_getReservationStatus","summary":"Whether every hold this order should have exists yet","description":"Compares each line (kits flattened to leaf components) against the holds on record. `complete: false` means reservation writing has not finished — or failed part-way — and a pick list printed now would be missing items.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/reservations/expand-kits":{"post":{"operationId":"OrdersController_expandKitHolds","summary":"Re-expand kit-level holds on an order into component holds","description":"Orders written before bundle expansion went recursive can hold reservations against the kit SKU itself — a virtual variant that owns no stock, so fulfilling moves nothing and the pick list names a SKU on no shelf. This replaces each open kit-level hold with the leaf-component holds the current checkout would create. Idempotent; holds that are shipped, PO-linked, or on kits with no components are left alone and reported.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/lines/{lineId}/refresh-holds":{"post":{"operationId":"OrdersController_refreshLineHolds","summary":"Rebuild one line's stock holds from the current catalog item","description":"Re-flattens the line against its catalog item as it stands today and reconciles the stock holds to match. Use after fixing an item mid-fulfilment — a SIMPLE corrected to a BUNDLE, a kit whose components changed — so the pick list pulls the right things. Refuses lines with committed (already fulfilled) holds or holds linked to a purchase order, and finished orders.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"lineId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["orders"]}},"/api/v1/orders/release-stale-holds":{"post":{"operationId":"OrdersController_releaseStaleHolds","summary":"Release open stock holds left behind on orders that already shipped","description":"A hold should end when its order does, but orders fulfilled before hold commitment was wired up (or whose webhook was missed) left holds open forever — each one inflating its variant’s reserved total and understating sellable stock on every channel. Dry run unless dryRun is explicitly false; the dry run returns exactly what would be released.","parameters":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"dryRun":{"type":"boolean","default":true,"description":"Report what would be released without writing anything; pass false explicitly to release","example":true},"limit":{"type":"number","default":500,"description":"Maximum holds to examine in one run","example":500}}}}}},"responses":{"201":{"description":""}},"tags":["orders"]}},"/api/v1/orders/reresolve-lines":{"post":{"operationId":"OrdersController_reresolveLines","summary":"Re-match order lines that were never linked to a variant","description":"Line matching only ran at order-creation time, so a line orphaned by a missing channel listing held no stock and triggered no build forever. This re-runs it. Dry run unless dryRun is explicitly false. Never moves stock: reservations are created only for orders that can still be fulfilled, and already-shipped orders are linked and reported for a human to decide on.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReresolveLinesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReresolveLinesResultDto"}}}},"201":{"description":""}},"tags":["orders"]}},"/api/v1/orders/bulk/status":{"put":{"operationId":"OrdersController_bulkUpdateStatus","summary":"Bulk-update status for multiple orders","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpdateOrderStatusDto"}}}},"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/status":{"put":{"operationId":"OrdersController_updateStatus","summary":"Update an order's status","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrderStatusDto"}}}},"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/notes":{"put":{"operationId":"OrdersController_updateNotes","summary":"Update an order's notes","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["notes"],"properties":{"notes":{"type":"string","description":"Replacement notes text for the order","example":"Customer asked for signature on delivery"}}}}}},"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/shipping-address":{"put":{"operationId":"OrdersController_updateShippingAddress","summary":"Update an order's shipping address","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["shippingAddress"],"properties":{"shippingAddress":{"type":"object","additionalProperties":true,"description":"Replacement shipping address, stored as given (free-form JSON — the shape the order's sales channel supplied)","example":{"firstName":"Jane","lastName":"Doe","street1":"123 Main St","city":"Portland","state":"OR","zip":"97201","country":"US"}}}}}}},"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/restore":{"post":{"operationId":"OrdersController_restore","summary":"Restore a soft-deleted order","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/fulfill":{"post":{"operationId":"OrdersController_fulfill","summary":"Mark an order fulfilled and deduct its stock","description":"For fulfillment that does not come back through a channel or ShipStation — a counter collection, or delivering it yourself. Deducts stock for every open reservation on the order (dropship lines excepted: those never sat in your warehouse). Safe to call twice — a reservation already fulfilled is skipped.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FulfillOrderDto"}}}},"responses":{"201":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/restore-and-fulfill-holds":{"post":{"operationId":"OrdersController_restoreAndFulfillHolds","summary":"Restore holds that were released on a shipped order, then fulfil them","description":"Recovery for holds cancelled by a stale-hold sweep on an order that had actually shipped — the units left, nothing was deducted, and no open hold remains to fulfil from. Puts the named holds back exactly as they were and runs them through normal fulfilment, so the stock comes down as a real SALE movement attributed to this order. You must name the reservation ids: a line the customer genuinely removed is also a cancelled hold on a shipped order, and deducting for it would invent a shipment. Holds that are not CANCELLED — a COMMITTED one above all — are skipped and reported, never deducted twice.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reservationIds"],"properties":{"reservationIds":{"type":"array","items":{"type":"string"},"description":"Ids of the CANCELLED holds on this order to restore and fulfil"}}}}}},"responses":{"201":{"description":""}},"tags":["orders"]}},"/api/v1/orders/{id}/sync":{"post":{"operationId":"OrdersController_syncFromChannel","summary":"Re-pull an order from its sales channel","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["orders"]}},"/api/v1/orders/by-status":{"delete":{"operationId":"OrdersController_deleteByStatus","summary":"Delete orders by channel and status (dry-run supported)","parameters":[{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"dryRun","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["orders"]}},"/api/v1/reservations/{id}/allocate":{"post":{"operationId":"ReservationsController_allocateReservation","summary":"Allocate a reservation to a source channel","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sourceChannelId"],"properties":{"sourceChannelId":{"type":"string","description":"Source (inbound) channel to allocate the reservation to — allowed while the reservation is PENDING or ALLOCATED","example":"5e6f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9a0b"}}}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["reservations"]}},"/api/v1/reservations/{id}/deallocate":{"post":{"operationId":"ReservationsController_deallocateReservation","summary":"Reverse a reservation allocation (return to PENDING)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["reservations"]}},"/api/v1/reservations/{id}/available-sources":{"get":{"operationId":"ReservationsController_getAvailableSources","summary":"List available source channels for a reservation","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["reservations"]}},"/api/v1/stock/adjust":{"post":{"operationId":"StockController_adjustStock","summary":"Adjust on-hand stock for a variant at a warehouse (with movement reason)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdjustStockDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["stock"]}},"/api/v1/stock/movements":{"get":{"operationId":"StockController_getMovements","summary":"List stock movements (filter by variant, warehouse, type)","parameters":[{"name":"variantId","required":true,"in":"query","schema":{"type":"string"}},{"name":"warehouseId","required":true,"in":"query","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["stock"]}},"/api/v1/stock/variant/{variantId}/breakdown":{"get":{"operationId":"StockController_getVariantBreakdown","summary":"Per-warehouse stock breakdown for a variant","parameters":[{"name":"variantId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["stock"]}},"/api/v1/stock/items/{variantId}/{warehouseId}":{"patch":{"operationId":"StockController_updateItem","summary":"Update a stock item (bin location, reorder point, etc.)","description":"Edits min/max quantities on one stock location. Does not change on-hand quantity or relocate stock — use /stock/adjust or /stock/move for that.","parameters":[{"name":"variantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"warehouseId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"binId":{"type":"string","nullable":true,"description":"Which bin location to edit (null or omitted = the Unassigned location). Selects the row; it is not changed.","example":"3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"},"minQuantity":{"type":"number","description":"Reorder point — minimum quantity to keep at this location","example":5},"maxQuantity":{"type":"number","description":"Maximum quantity to stock at this location","example":50}}}}}},"responses":{"200":{"description":""}},"tags":["stock"]}},"/api/v1/stock/transfer":{"post":{"operationId":"StockController_transfer","summary":"Transfer stock between warehouses","description":"Records a TRANSFER_OUT movement at the source and TRANSFER_IN at the destination. The source bin is resolved automatically (primary bin, else the single bin, else the fullest); fails if the resolved location holds less than the requested quantity.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["variantId","fromWarehouseId","toWarehouseId","quantity"],"properties":{"variantId":{"type":"string","description":"Variant to transfer","example":"0d9b1d5e-7c2a-4f6e-9b1a-2c3d4e5f6a7b"},"fromWarehouseId":{"type":"string","description":"Source warehouse","example":"7b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"},"toWarehouseId":{"type":"string","description":"Destination warehouse (must differ from the source)","example":"9d0e1f2a-3b4c-5d6e-7f8a-9b0c1d2e3f4a"},"quantity":{"type":"number","description":"Units to transfer (positive)","example":10},"notes":{"type":"string","description":"Free-text note appended to both movement reasons","example":"Rebalancing ahead of the holiday rush"}}}}}},"responses":{"201":{"description":""}},"tags":["stock"]}},"/api/v1/stock/move":{"post":{"operationId":"StockController_move","summary":"Move stock to a different bin location within a warehouse","description":"Put-away: relocates units between bins in one warehouse. Net-zero across the warehouse, so available quantity is unchanged; a TRANSFER_OUT/TRANSFER_IN movement pair records the relocation.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["variantId","warehouseId","fromBinId","toBinId","quantity"],"properties":{"variantId":{"type":"string","description":"Variant to move","example":"0d9b1d5e-7c2a-4f6e-9b1a-2c3d4e5f6a7b"},"warehouseId":{"type":"string","description":"Warehouse the move happens within","example":"7b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"},"fromBinId":{"type":"string","nullable":true,"description":"Bin to move from (null = the Unassigned location)","example":null},"toBinId":{"type":"string","nullable":true,"description":"Bin to move to (null = the Unassigned location; must differ from fromBinId)","example":"3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"},"quantity":{"type":"number","description":"Units to move (positive; must not exceed on-hand at the source location)","example":4}}}}}},"responses":{"201":{"description":""}},"tags":["stock"]}},"/api/v1/stock/primary-bin":{"post":{"operationId":"StockController_setPrimaryBin","summary":"Designate the bin a SKU is normally pulled from","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPrimaryBinDto"}}}},"responses":{"201":{"description":""}},"tags":["stock"]}},"/api/v1/warehouses/{warehouseId}/stock":{"get":{"operationId":"WarehouseStockController_getWarehouseStock","summary":"List stock items in a warehouse (searchable, paginated)","parameters":[{"name":"warehouseId","required":true,"in":"path","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}},{"name":"includeEmpty","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortBy","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortOrder","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["stock"]}},"/api/v1/warehouses/{warehouseId}/stock/movements":{"get":{"operationId":"WarehouseStockController_getWarehouseMovements","summary":"List stock movements for a warehouse","parameters":[{"name":"warehouseId","required":true,"in":"path","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["stock"]}},"/api/v1/warehouses/{warehouseId}/stock/export-template":{"get":{"operationId":"WarehouseStockController_exportTemplate","summary":"Download a CSV stock-count template for this warehouse","parameters":[{"name":"warehouseId","required":true,"in":"path","schema":{"type":"string"}},{"name":"scope","required":true,"in":"query","schema":{"type":"string"}},{"name":"includeEmpty","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["stock"]}},"/api/v1/warehouses/{warehouseId}/stock/count/preview":{"post":{"operationId":"WarehouseStockController_previewCount","summary":"Preview a bulk stock count (diff against current on-hand)","parameters":[{"name":"warehouseId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkCountPreviewDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["stock"]}},"/api/v1/warehouses/{warehouseId}/stock/count/apply":{"post":{"operationId":"WarehouseStockController_applyCount","summary":"Apply a bulk stock count","parameters":[{"name":"warehouseId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkCountApplyDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["stock"]}},"/api/v1/products":{"get":{"operationId":"ProductsController_findAll","summary":"List products (filter by brand, category, channel, type, search, matched/enriched/archived)","parameters":[{"name":"brandId","required":true,"in":"query","schema":{"type":"string"}},{"name":"categoryId","required":true,"in":"query","schema":{"type":"string"}},{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"matched","required":true,"in":"query","schema":{"type":"string"}},{"name":"enriched","required":true,"in":"query","schema":{"type":"string"}},{"name":"archived","required":true,"in":"query","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"marginBelow","required":true,"in":"query","schema":{"type":"string"}},{"name":"marginUnknown","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortBy","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortOrder","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["products"]},"post":{"operationId":"ProductsController_create","summary":"Create a product","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["products"]}},"/api/v1/products/{id}/where-used":{"get":{"operationId":"ProductsController_whereUsed","summary":"List assemblies and bundles that use this product as a component","description":"Where this product is used as a component: every assembly (BOM) and bundle\nthat includes any of the product's variants.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["products"]}},"/api/v1/products/trash":{"get":{"operationId":"ProductsController_findDeleted","summary":"List soft-deleted (trashed) products","parameters":[{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["products"]}},"/api/v1/products/data-health":{"get":{"operationId":"ProductsController_dataHealth","summary":"Get catalog data-health report for the org","parameters":[],"responses":{"200":{"description":""}},"tags":["products"]}},"/api/v1/products/family-candidates":{"get":{"operationId":"ProductsController_familyCandidates","summary":"Detect SIMPLE products that are channel siblings but not family-linked","description":"Detect groups of Foundry SIMPLE products that are siblings on an outbound\nchannel but aren't family-linked in Foundry. Used by the \"Family\nCandidates\" tool to consolidate stranded siblings.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["products"]}},"/api/v1/products/{id}":{"get":{"operationId":"ProductsController_findOne","summary":"Get a product by id","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["products"]},"put":{"operationId":"ProductsController_update","summary":"Update a product","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["products"]},"delete":{"operationId":"ProductsController_delete","summary":"Soft-delete a product","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["products"]}},"/api/v1/products/{id}/template-status":{"get":{"operationId":"ProductsController_templateStatus","summary":"Get a product's PIM template status","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["products"]}},"/api/v1/products/{id}/duplicate":{"post":{"operationId":"ProductsController_duplicate","summary":"Duplicate a product","description":"Copies a SIMPLE, BUNDLE or ASSEMBLY product — descriptive fields, brand/category/class, custom fields, images, price/cost/dimensions, the active BOM and bundle components — into a new product with a fresh Foundry SKU. SKU is blank unless supplied; UPC/GTIN/MPN, stock, listings and enrichment are not copied. Families are refused.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuplicateProductDto"}}}},"responses":{"201":{"description":""}},"tags":["products"]}},"/api/v1/products/variant-parent":{"post":{"operationId":"ProductsController_createVariantParent","summary":"Create a FAMILY parent product from existing child products","description":"Create a variant parent product from existing products.\nThe specified child products will be linked to the new parent.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVariantParentDto"}}}},"responses":{"201":{"description":""}},"tags":["products"]}},"/api/v1/products/{id}/children":{"post":{"operationId":"ProductsController_linkChildren","summary":"Link products as children to a parent product","description":"Link products as children to a parent product.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkChildrenDto"}}}},"responses":{"201":{"description":""}},"tags":["products"]}},"/api/v1/products/{id}/children/{childId}":{"delete":{"operationId":"ProductsController_unlinkChild","summary":"Unlink a child product from its parent","description":"Unlink a child product from its parent.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"childId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["products"]}},"/api/v1/products/{id}/children/order":{"put":{"operationId":"ProductsController_reorderChildren","summary":"Reorder children under a FAMILY parent","description":"Reorder children under a FAMILY parent. Body: { orderedChildIds: [...] }.\nEach child's `position` is set to its index in the array; children not\nin the list retain their existing position.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReorderChildrenDto"}}}},"responses":{"200":{"description":""}},"tags":["products"]}},"/api/v1/products/{id}/convert/preflight":{"get":{"operationId":"ProductsController_convertPreflight","summary":"Preflight check before changing a product's type","description":"Preflight check for changing a product's type. Returns blockers\n(hard-fail) and warnings (acknowledge-required) so the UI can render\na confirmation dialog before calling POST /:id/convert.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"targetType","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["products"]}},"/api/v1/products/{id}/convert":{"post":{"operationId":"ProductsController_convert","summary":"Change a product's type (e.g. SIMPLE→FAMILY/BUNDLE/ASSEMBLY)","description":"Change a product's type. Phase 1 supports SIMPLE→FAMILY,\nSIMPLE→BUNDLE, SIMPLE→ASSEMBLY, BUNDLE→ASSEMBLY. Pure type flip plus\n(for BUNDLE→ASSEMBLY) a BOM bootstrapped from existing bundle rows.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConvertProductDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["products"]}},"/api/v1/products/bulk-from-source":{"post":{"operationId":"ProductsController_bulkCreateFromSource","summary":"Bulk-create products and variants from source channel SKUs","description":"Bulk create products from source channel SKUs.\nCreates products and variants, linking source SKUs in the process.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkCreateFromSourceDto"}}}},"responses":{"201":{"description":""}},"tags":["products"]}},"/api/v1/products/bulk-restore":{"post":{"operationId":"ProductsController_bulkRestore","summary":"Bulk-restore soft-deleted products (by ids, all, or date range)","description":"Bulk restore soft-deleted products.\n- { ids: [...] } restores specific products\n- { all: true } restores every product in the trash\n- { after, before } restores products deleted within a date range","parameters":[],"responses":{"200":{"description":""}},"tags":["products"]}},"/api/v1/products/{id}/restore":{"post":{"operationId":"ProductsController_restore","summary":"Restore a soft-deleted product","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["products"]}},"/api/v1/products/{id}/archive":{"post":{"operationId":"ProductsController_archive","summary":"Archive a product","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["products"]}},"/api/v1/products/{id}/unarchive":{"post":{"operationId":"ProductsController_unarchive","summary":"Unarchive a product","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["products"]}},"/api/v1/products/bulk":{"delete":{"operationId":"ProductsController_bulkDelete","summary":"Bulk soft-delete products (by ids, or all with confirm)","description":"Bulk delete products (soft-delete)\n- Pass { ids: ['id1', 'id2'] } to delete specific products\n- Pass { all: true, confirm: \"DELETE_ALL_PRODUCTS\" } to delete all products","parameters":[],"responses":{"200":{"description":""}},"tags":["products"]}},"/api/v1/variants":{"get":{"operationId":"VariantsController_findAll","summary":"List variants (filter by product, option type, search)","parameters":[{"name":"productId","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"optionType","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["variants"]},"post":{"operationId":"VariantsController_create","summary":"Create a variant","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVariantDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["variants"]}},"/api/v1/variants/duplicates":{"get":{"operationId":"VariantsController_detectDuplicates","summary":"Detect duplicate variants","parameters":[{"name":"matchBy","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["variants"]}},"/api/v1/variants/sourcing-coverage":{"get":{"operationId":"VariantsController_sourcingCoverage","summary":"Sourcing coverage, resolved through bundle/assembly components","description":"Sourcing coverage resolved THROUGH bundle components and BOM lines.\n\nPrefer this over `needing-suppliers` (and the `hasSource` channel-SKU\nfilter) for anything involving kits: both of those ask only whether the\nvariant itself carries a supplier row, so every BUNDLE and ASSEMBLY reads\nas unsourced no matter how well sourced its components are — the count of\n\"unsourced\" products goes UP as you convert families to kits.\n\n`gaps` is the work list: a kit never needs a supplier of its own, so every\nfix lands on a leaf part, and one leaf can unblock many products.","parameters":[{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}},{"name":"variantIds","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["variants"]}},"/api/v1/variants/needing-suppliers":{"get":{"operationId":"VariantsController_findNeedingSuppliers","summary":"List variants that have no linked supplier","description":"Variants that cannot be traced to anything purchasable.\n\nStructure-aware since the stored `sourcingStatus` landed: a fully-sourced\nBUNDLE or ASSEMBLY is no longer listed here just for holding no supplier row\nof its own. Use `sourcing-coverage` when you want the gap leaves and what\neach one blocks rather than a flat list.","parameters":[{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["variants"]}},"/api/v1/variants/export":{"get":{"operationId":"VariantsController_exportCsv","summary":"Export variants to CSV (by ids or filters)","description":"Export variants to CSV.\nGET /variants/export?ids=a,b,c        (multi-select export)\nGET /variants/export?productId=xxx    (filter export)\nWhen `ids` is provided, all other filter params are ignored.","parameters":[{"name":"ids","required":true,"in":"query","schema":{"type":"string"}},{"name":"productIds","required":true,"in":"query","schema":{"type":"string"}},{"name":"productId","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"brandId","required":true,"in":"query","schema":{"type":"string"}},{"name":"matched","required":true,"in":"query","schema":{"type":"string"}},{"name":"enriched","required":true,"in":"query","schema":{"type":"string"}},{"name":"productStatus","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["variants"]}},"/api/v1/variants/import":{"post":{"operationId":"VariantsController_importCsv","summary":"Import variants from an uploaded CSV file","parameters":[],"responses":{"201":{"description":""}},"tags":["variants"]}},"/api/v1/variants/{id}":{"get":{"operationId":"VariantsController_findOne","summary":"Get a variant by id","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["variants"]},"put":{"operationId":"VariantsController_update","summary":"Update a variant","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVariantDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["variants"]},"delete":{"operationId":"VariantsController_delete","summary":"Soft-delete a variant","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["variants"]}},"/api/v1/variants/{id}/where-used":{"get":{"operationId":"VariantsController_whereUsed","summary":"List BOMs where this variant is used as a component","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["variants"]}},"/api/v1/variants/{id}/supplier-suggestions":{"get":{"operationId":"VariantsController_getSupplierSuggestions","summary":"Get supplier suggestions for a variant","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["variants"]}},"/api/v1/variants/{id}/restore":{"post":{"operationId":"VariantsController_restore","summary":"Restore a soft-deleted variant","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["variants"]}},"/api/v1/variants/{id}/use-source-cost":{"post":{"operationId":"VariantsController_useSourceCost","summary":"Clear the manual-cost lock and re-resolve cost from sources","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["variants"]}},"/api/v1/variants/aggregate-inventory":{"post":{"operationId":"VariantsController_aggregateInventory","summary":"Trigger inventory aggregation across all variants","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["variants"]}},"/api/v1/variants/{id}/sources":{"get":{"operationId":"VariantsController_getAvailableSources","summary":"List available sourcing options for a variant","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["variants"]}},"/api/v1/variants/{id}/upload-url":{"get":{"operationId":"VariantsController_getImageUploadUrl","summary":"Get a presigned S3 URL to upload a variant image","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"filename","required":true,"in":"query","schema":{"type":"string"}},{"name":"contentType","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["variants"]}},"/api/v1/variants/{id}/images":{"post":{"operationId":"VariantsController_addImage","summary":"Add an image to a variant","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["variants"]},"delete":{"operationId":"VariantsController_removeImage","summary":"Remove an image from a variant","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["variants"]}},"/api/v1/variants/{id}/images/from-url":{"post":{"operationId":"VariantsController_addImagesFromUrl","summary":"Attach image(s) to a variant by URL, re-hosting external URLs into S3","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["variants"]}},"/api/v1/variants/{id}/images/reorder":{"put":{"operationId":"VariantsController_reorderImages","summary":"Reorder a variant's images","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["variants"]}},"/api/v1/bundles/export":{"get":{"operationId":"BundlesController_exportBundles","summary":"Export bundle component lists to CSV (optionally by bundle variant ids)","parameters":[{"name":"variantIds","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["bundles"]}},"/api/v1/bundles/import":{"post":{"operationId":"BundlesController_importBundles","summary":"Import bundle component lists from an uploaded CSV file","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["bundles"]}},"/api/v1/bundles/{bundleVariantId}/components":{"get":{"operationId":"BundlesController_getComponents","summary":"List a bundle's components with each component variant's stock and cost figures","parameters":[{"name":"bundleVariantId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["bundles"]},"post":{"operationId":"BundlesController_addComponent","summary":"Add a component variant to a bundle","description":"Upserts: adding a variant already in the bundle overwrites its quantity. Self-reference and transitive component cycles are rejected.","parameters":[{"name":"bundleVariantId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddComponentDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["bundles"]}},"/api/v1/bundles/{bundleVariantId}/components/{componentVariantId}":{"put":{"operationId":"BundlesController_updateQuantity","summary":"Update a bundle component's per-bundle quantity","parameters":[{"name":"bundleVariantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"componentVariantId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateQuantityDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["bundles"]},"delete":{"operationId":"BundlesController_removeComponent","summary":"Remove a component variant from a bundle","parameters":[{"name":"bundleVariantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"componentVariantId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["bundles"]}},"/api/v1/bundles/{bundleVariantId}/availability":{"get":{"operationId":"BundlesController_getAvailability","summary":"Get how many of this bundle can be assembled from component stock on hand","description":"Computed live over the flattened leaf components, netting reserved quantity off available quantity — the same figure the aggregation job stores as buildableQuantity.","parameters":[{"name":"bundleVariantId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["bundles"]}},"/api/v1/brands":{"get":{"operationId":"BrandsController_findAll","summary":"List all brands with product counts","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["brands"]},"post":{"operationId":"BrandsController_create","summary":"Create a brand","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBrandDto"}}}},"responses":{"201":{"description":""}},"tags":["brands"]}},"/api/v1/brands/from-sources":{"get":{"operationId":"BrandsController_findFromSources","summary":"Distinct brands found on supplier-source SKUs, with promote status","description":"Distinct brands found on source (INBOUND) channel SKUs, with SKU/source counts\nand whether a matching catalog Brand record exists.","parameters":[],"responses":{"200":{"description":""}},"tags":["brands"]}},"/api/v1/brands/{id}":{"put":{"operationId":"BrandsController_update","summary":"Update a brand (name, slug, descriptions)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBrandDto"}}}},"responses":{"200":{"description":""}},"tags":["brands"]},"delete":{"operationId":"BrandsController_delete","summary":"Move a brand to trash (soft delete)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["brands"]},"get":{"operationId":"BrandsController_getOne","summary":"Get a single brand (name, slug, descriptions)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["brands"]}},"/api/v1/brands/trash":{"get":{"operationId":"BrandsController_listTrash","summary":"List soft-deleted brands","parameters":[],"responses":{"200":{"description":""}},"tags":["brands"]}},"/api/v1/brands/bulk-restore":{"post":{"operationId":"BrandsController_bulkRestore","summary":"Restore multiple brands from trash","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkIdsDto"}}}},"responses":{"201":{"description":""}},"tags":["brands"]}},"/api/v1/brands/{id}/restore":{"post":{"operationId":"BrandsController_restore","summary":"Restore a brand from trash","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["brands"]}},"/api/v1/brands/{id}/merge":{"post":{"operationId":"BrandsController_merge","summary":"Merge a brand into another (moves products, deletes source)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeBrandDto"}}}},"responses":{"201":{"description":""}},"tags":["brands"]}},"/api/v1/brands/export":{"get":{"operationId":"BrandsController_exportCsv","summary":"Export brands as CSV","parameters":[],"responses":{"200":{"description":""}},"tags":["brands"]}},"/api/v1/brands/import":{"post":{"operationId":"BrandsController_importCsv","summary":"Import brands from CSV (single name column)","description":"Creates a brand per row, skipping names that already exist (case-insensitive). An optional `name` header row is ignored. Returns created/skipped counts and per-row errors.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["csv"],"properties":{"csv":{"type":"string","description":"Raw CSV text: one brand name per line; optional \"name\" header row.","example":"name\nFiamma\nDometic"}}}}}},"responses":{"201":{"description":""}},"tags":["brands"]}},"/api/v1/categories":{"get":{"operationId":"CategoriesController_findAll","summary":"List all categories (org-global, hierarchical, in display order)","parameters":[],"responses":{"200":{"description":""}},"tags":["categories"]},"post":{"operationId":"CategoriesController_create","summary":"Create a category","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCategoryDto"}}}},"responses":{"201":{"description":""}},"tags":["categories"]}},"/api/v1/categories/trash":{"get":{"operationId":"CategoriesController_listTrash","summary":"List soft-deleted categories","parameters":[],"responses":{"200":{"description":""}},"tags":["categories"]}},"/api/v1/categories/bulk-restore":{"post":{"operationId":"CategoriesController_bulkRestore","summary":"Restore multiple categories from trash","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkIdsDto"}}}},"responses":{"201":{"description":""}},"tags":["categories"]}},"/api/v1/categories/{id}/restore":{"post":{"operationId":"CategoriesController_restore","summary":"Restore a category from trash","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["categories"]}},"/api/v1/categories/reorder":{"put":{"operationId":"CategoriesController_reorder","summary":"Reorder sibling categories","description":"Sets display order for the children of `parentId` (null/omitted = root level) from the `orderedIds` array. `position` is derived and read-only on item PUT.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReorderCategoriesDto"}}}},"responses":{"200":{"description":""}},"tags":["categories"]}},"/api/v1/categories/{id}":{"put":{"operationId":"CategoriesController_update","summary":"Update a category (name, slug, parent)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCategoryDto"}}}},"responses":{"200":{"description":""}},"tags":["categories"]},"delete":{"operationId":"CategoriesController_delete","summary":"Move a category to trash (soft delete)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["categories"]},"get":{"operationId":"CategoriesController_getOne","summary":"Get a single category (name, slug, parent, descriptions)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["categories"]}},"/api/v1/warehouses":{"get":{"operationId":"WarehousesController_findAll","summary":"List warehouses","parameters":[],"responses":{"200":{"description":""}},"tags":["warehouses"]},"post":{"operationId":"WarehousesController_create","summary":"Create a warehouse","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWarehouseDto"}}}},"responses":{"201":{"description":""}},"tags":["warehouses"]}},"/api/v1/warehouses/trash":{"get":{"operationId":"WarehousesController_listTrash","summary":"List trashed warehouses","parameters":[],"responses":{"200":{"description":""}},"tags":["warehouses"]}},"/api/v1/warehouses/{id}":{"get":{"operationId":"WarehousesController_findOne","summary":"Get a warehouse by ID","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["warehouses"]},"patch":{"operationId":"WarehousesController_update","summary":"Update a warehouse","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWarehouseDto"}}}},"responses":{"200":{"description":""}},"tags":["warehouses"]},"delete":{"operationId":"WarehousesController_delete","summary":"Soft-delete a warehouse (move to trash)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["warehouses"]}},"/api/v1/warehouses/bulk-restore":{"post":{"operationId":"WarehousesController_bulkRestore","summary":"Bulk-restore trashed warehouses","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ids"],"properties":{"ids":{"type":"array","items":{"type":"string"},"description":"Ids of the trashed warehouses to restore."}}}}}},"responses":{"201":{"description":""}},"tags":["warehouses"]}},"/api/v1/warehouses/{id}/restore":{"post":{"operationId":"WarehousesController_restore","summary":"Restore a trashed warehouse","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["warehouses"]}},"/api/v1/warehouses/{id}/bins":{"get":{"operationId":"WarehousesController_listBins","summary":"List bins at a warehouse","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["warehouses"]},"post":{"operationId":"WarehousesController_createBin","summary":"Create a bin at a warehouse","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBinDto"}}}},"responses":{"201":{"description":""}},"tags":["warehouses"]}},"/api/v1/warehouses/{id}/bins/{binId}":{"patch":{"operationId":"WarehousesController_renameBin","summary":"Rename a bin","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"binId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBinDto"}}}},"responses":{"200":{"description":""}},"tags":["warehouses"]},"delete":{"operationId":"WarehousesController_deleteBin","summary":"Delete an empty bin","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"binId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["warehouses"]}},"/api/v1/option-types":{"get":{"operationId":"OptionTypesController_findAll","summary":"List the org's option types with product usage counts","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["option-types"]},"post":{"operationId":"OptionTypesController_create","summary":"Create an option type (e.g. Size, Color)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOptionTypeDto"}}}},"responses":{"201":{"description":""}},"tags":["option-types"]}},"/api/v1/option-types/{id}":{"put":{"operationId":"OptionTypesController_update","summary":"Update an option type (name, position)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOptionTypeDto"}}}},"responses":{"200":{"description":""}},"tags":["option-types"]},"delete":{"operationId":"OptionTypesController_delete","summary":"Delete an unused option type","description":"Fails with 409 if the option type is still used by any product option.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["option-types"]}},"/api/v1/products/{productId}/options":{"get":{"operationId":"ProductOptionsController_findAll","summary":"List a product's options in display order","parameters":[{"name":"productId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["option-types"]},"post":{"operationId":"ProductOptionsController_create","summary":"Add an option to a product","description":"Appends at the end of the display order when `position` is omitted.","parameters":[{"name":"productId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductOptionDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["option-types"]}},"/api/v1/products/{productId}/options/{optionId}":{"put":{"operationId":"ProductOptionsController_update","summary":"Update a product option (name, position)","parameters":[{"name":"productId","required":true,"in":"path","schema":{"type":"string"}},{"name":"optionId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductOptionDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["option-types"]},"delete":{"operationId":"ProductOptionsController_delete","summary":"Remove an option from a product","description":"Also deletes every variant's stored value for this option (cascade).","parameters":[{"name":"productId","required":true,"in":"path","schema":{"type":"string"}},{"name":"optionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["option-types"]}},"/api/v1/variants/{variantId}/options":{"get":{"operationId":"VariantOptionsController_findAll","summary":"List a variant's option values in option display order","parameters":[{"name":"variantId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["option-types"]},"put":{"operationId":"VariantOptionsController_setOptions","summary":"Replace a variant's option values","description":"Full replace: existing values are deleted and the given set is written. Each `productOptionId` must be an option on the variant's product or on its parent product (hierarchical families).","parameters":[{"name":"variantId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetVariantOptionsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["option-types"]}},"/api/v1/products/{id}/attachments":{"get":{"operationId":"ProductAttachmentsController_list","summary":"List a product's attachments with presigned download URLs","description":"Includes the parent product's attachments (flagged `inherited: true`) for child products. `fileUrl` is a presigned S3 download URL valid for 1 hour.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["product-attachments"]},"post":{"operationId":"ProductAttachmentsController_create","summary":"Register an uploaded file as a product attachment","description":"Step 2 of the upload flow: records the file uploaded via the presigned URL from POST /products/:id/attachments/upload-url.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label","fileName","fileUrl","contentType","sizeBytes"],"properties":{"label":{"type":"string","description":"Display label for the attachment (required, trimmed)."},"fileName":{"type":"string","description":"Original file name."},"fileUrl":{"type":"string","description":"The `fileUrl` returned by the upload-url endpoint."},"s3Key":{"type":"string","description":"The `s3Key` returned by the upload-url endpoint; derived from `fileUrl` when omitted."},"contentType":{"type":"string","description":"MIME type of the file.","example":"application/pdf"},"sizeBytes":{"type":"number","description":"File size in bytes."}}}}}},"responses":{"201":{"description":""}},"tags":["product-attachments"]}},"/api/v1/products/{id}/attachments/upload-url":{"post":{"operationId":"ProductAttachmentsController_createUploadUrl","summary":"Get a presigned S3 upload URL for a product attachment","description":"Step 1 of the upload flow: PUT the file bytes to the returned `uploadUrl` (valid 1 hour), then register it via POST /products/:id/attachments with the returned `fileUrl`/`s3Key`. Allowed types: PDF, PNG, JPEG, DOC, DOCX. Max size 10MB.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fileName","contentType","sizeBytes"],"properties":{"fileName":{"type":"string","description":"Original file name; sanitized into the S3 key."},"contentType":{"type":"string","description":"MIME type of the file. Must be one of: application/pdf, image/png, image/jpeg, image/jpg, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document.","example":"application/pdf"},"sizeBytes":{"type":"number","description":"File size in bytes; max 10485760 (10MB)."}}}}}},"responses":{"201":{"description":""}},"tags":["product-attachments"]}},"/api/v1/product-attachments/{attachmentId}":{"delete":{"operationId":"ProductAttachmentsController_delete","summary":"Delete a product attachment (soft delete)","description":"The stored file is kept in S3; only the attachment record is soft-deleted.","parameters":[{"name":"attachmentId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["product-attachments"]}},"/api/v1/public/variant-images/{variantId}/{index}":{"get":{"operationId":"PublicVariantImagesController_redirect","summary":"Fetch a variant image by index (unauthenticated, 302 to a presigned URL)","description":"Short, stable URL per variant image for channel platforms to fetch from. `index` is the zero-based position in the variant's image list. S3-hosted images 302-redirect to a fresh short-lived presigned URL; external http(s) images redirect as-is; data: URIs are served inline. 404 when the variant or index does not exist.","parameters":[{"name":"variantId","required":true,"in":"path","schema":{"type":"string"}},{"name":"index","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["public-variant-images"]}},"/api/v1/channel-skus":{"post":{"operationId":"ChannelSkusController_create","summary":"Create a channel SKU (listing or supplier row) on a channel, optionally linked to a variant.","description":"Returns 409 if the channel already has a SKU with the same channelSku value.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateChannelSkuDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["channel-skus"]},"get":{"operationId":"ChannelSkusController_findAll","summary":"List channel SKUs with filtering, sorting, and offset pagination.","parameters":[{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}},{"name":"mapped","required":true,"in":"query","schema":{"type":"string"}},{"name":"hasSource","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"brand","required":true,"in":"query","schema":{"type":"string"}},{"name":"outOfStock","required":true,"in":"query","schema":{"type":"string"}},{"name":"updatedInLastImport","required":true,"in":"query","schema":{"type":"string"}},{"name":"variantIds","required":true,"in":"query","schema":{"type":"string"}},{"name":"presence","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortBy","required":true,"in":"query","schema":{"type":"string"}},{"name":"sortOrder","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["channel-skus"]}},"/api/v1/channel-skus/brands":{"get":{"operationId":"ChannelSkusController_getBrands","summary":"List the distinct brand names across channel SKUs, optionally scoped to one channel.","parameters":[{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["channel-skus"]}},"/api/v1/channel-skus/bulk":{"delete":{"operationId":"ChannelSkusController_bulkDelete","summary":"Bulk-delete channel SKUs on a channel, by explicit ids, unmapped-only, or the whole channel.","description":"Hard-deletes matching SKUs. With no `ids` in the body and no `unmappedOnly=true`, this deletes EVERY SKU on the channel and requires `confirm: \"DELETE_ALL_SKUS\"` in the body. Deleting mapped supplier rows flags the affected variants for a sourcing-status recompute.","parameters":[{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}},{"name":"unmappedOnly","required":true,"in":"query","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"},"description":"Specific channel SKU ids to delete. When present, other criteria are ignored."},"confirm":{"type":"string","description":"Must be \"DELETE_ALL_SKUS\" when deleting every SKU on the channel (no ids, unmappedOnly not true).","example":"DELETE_ALL_SKUS"}}}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["channel-skus"]},"put":{"operationId":"ChannelSkusController_bulkUpdate","summary":"Set one or more fields to the same value on many channel SKUs at once.","description":"Every field present in `data` is written identically to every SKU in `ids`. Requires a non-empty `ids` array and at least one field in `data`. Returns the number of SKUs updated.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ids","data"],"properties":{"ids":{"type":"array","items":{"type":"string"},"description":"Channel SKU ids to update."},"data":{"type":"object","description":"Field values applied to every listed SKU.","properties":{"brand":{"type":"string","description":"Brand name."},"cost":{"type":"number","description":"Supplier unit cost."},"mpn":{"type":"string","description":"Manufacturer part number."},"upc":{"type":"string","description":"UPC barcode."},"gtin":{"type":"string","description":"GTIN barcode."}}}}}}}},"responses":{"200":{"description":""}},"tags":["channel-skus"]}},"/api/v1/channel-skus/export":{"get":{"operationId":"ChannelSkusController_exportCsv","summary":"Export channel SKUs to a CSV download.","description":"When `ids` is provided, all other filter params are ignored.","parameters":[{"name":"ids","required":true,"in":"query","schema":{"type":"string"}},{"name":"channelId","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"mapped","required":true,"in":"query","schema":{"type":"string"}},{"name":"brand","required":true,"in":"query","schema":{"type":"string"}},{"name":"outOfStock","required":true,"in":"query","schema":{"type":"string"}},{"name":"hasSource","required":true,"in":"query","schema":{"type":"string"}},{"name":"updatedInLastImport","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["channel-skus"]}},"/api/v1/channel-skus/{id}":{"get":{"operationId":"ChannelSkusController_findOne","summary":"Get one channel SKU with its channel and linked variant details.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["channel-skus"]},"put":{"operationId":"ChannelSkusController_update","summary":"Update a channel SKU's fields, including its sale price window.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateChannelSkuDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["channel-skus"]},"delete":{"operationId":"ChannelSkusController_remove","summary":"Permanently delete a channel SKU.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["channel-skus"]}},"/api/v1/channel-skus/{id}/push":{"post":{"operationId":"ChannelSkusController_push","summary":"Push this one listing to its channel.","description":"Targeted alternative to POST /channels/:id/sync: pushes only this listing, not every listing on the channel. Optionally pass `fields` (sync-config field names, e.g. [\"price\",\"compareAtPrice\"]) to narrow further. A field the channel's Sync Settings do not push is skipped regardless of what is requested.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string"},"description":"Sync-config field names to push. Omit to push every field the channel is authoritative for."}}}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["channel-skus"]}},"/api/v1/channel-skus/{id}/link":{"post":{"operationId":"ChannelSkusController_link","summary":"Link a channel SKU to a catalog variant.","description":"Side effects: gap-fills the variant's option values and custom-field values from the SKU, backfills order lines that synced before the mapping existed, and flags the variant for a sourcing-status recompute. For INBOUND (supplier) channels it also triggers inventory aggregation, and when the source has a linked warehouse it creates a stock receipt for the SKU's inventory quantity.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkChannelSkuDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["channel-skus"]},"delete":{"operationId":"ChannelSkusController_unlink","summary":"Unlink a channel SKU from its variant.","description":"The previously linked variant is flagged for a sourcing-status recompute, since removing a supplier row can open a sourcing gap.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["channel-skus"]}},"/api/v1/channel-skus/{id}/suggestions":{"get":{"operationId":"ChannelSkusController_getSuggestions","summary":"Suggest catalog variants that likely match this channel SKU, with a confidence level and match reason.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["channel-skus"]}},"/api/v1/channel-skus/{id}/change-history":{"get":{"operationId":"ChannelSkusController_getChangeHistory","summary":"List a channel SKU's field-change log entries, newest first, with offset pagination.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"offset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["channel-skus"]}},"/api/v1/channel-skus/bulk-import":{"post":{"operationId":"ChannelSkusController_bulkImport","summary":"Import a multi-source CSV: rows are split by their source column and one import job is queued per source.","description":"Multipart upload (`file` + optional `mapping` JSON). Each distinct source value maps to an INBOUND channel by name, auto-creating channels that do not exist yet; rows with an empty source are skipped. Returns the parent job id and the per-source breakdown.","parameters":[],"responses":{"201":{"description":""}},"tags":["channel-skus"]}},"/api/v1/channel-skus/import":{"post":{"operationId":"ChannelSkusController_import","summary":"Upload a CSV of SKUs for one channel and queue an asynchronous import job.","description":"Multipart upload (`file` + `channelId`, optional `mapping` JSON and `delimiter`). Large files are split into chunks processed in parallel under a parent job. Poll GET /channel-skus/import/{jobId}/status for progress.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportChannelSkuDto"}}}},"responses":{"201":{"description":""}},"tags":["channel-skus"]}},"/api/v1/channel-skus/import/{jobId}/status":{"get":{"operationId":"ChannelSkusController_getImportStatus","summary":"Get live status and progress for an import job, aggregating chunked or per-source child jobs.","description":"Polling this endpoint also detects chunks stalled in PROCESSING for over 3 minutes and re-queues them.","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["channel-skus"]}},"/api/v1/channel-skus/import/{jobId}/cancel":{"post":{"operationId":"ChannelSkusController_cancelImport","summary":"Cancel a pending or processing import job (including all of its child jobs) and delete its uploaded files.","description":"Cancel a running or pending import job\nFor chunked jobs, cancels parent and all child jobs","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["channel-skus"]}},"/api/v1/channel-skus/import/{jobId}/retry":{"post":{"operationId":"ChannelSkusController_retryImport","summary":"Re-queue a failed or stalled import job, resuming from the last processed row.","description":"Completed and cancelled jobs cannot be retried, and bulk-import parent jobs must be retried per source job.","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["channel-skus"]}},"/api/v1/custom-fields":{"get":{"operationId":"CustomFieldDefsController_list","summary":"List the org's custom field definitions, optionally filtered by the entity type they apply to.","description":"Archived definitions are excluded unless `includeArchived=true`. `appliesTo` accepts PRODUCT, VARIANT, CHANNEL_SKU, CATEGORY, or BRAND; unrecognized values are ignored.","parameters":[{"name":"includeArchived","required":true,"in":"query","schema":{"type":"string"}},{"name":"appliesTo","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["custom-fields"]},"post":{"operationId":"CustomFieldDefsController_create","summary":"Create a custom field definition.","description":"The key must be unique in the org and match ^[a-z][a-z0-9_]*$. `appliesTo` defaults to [PRODUCT, VARIANT] when omitted.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomFieldDefDto"}}}},"responses":{"201":{"description":""}},"tags":["custom-fields"]}},"/api/v1/custom-fields/{id}":{"get":{"operationId":"CustomFieldDefsController_get","summary":"Get one custom field definition.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["custom-fields"]},"patch":{"operationId":"CustomFieldDefsController_update","summary":"Update a custom field definition (its key and type are immutable).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCustomFieldDefDto"}}}},"responses":{"200":{"description":""}},"tags":["custom-fields"]},"delete":{"operationId":"CustomFieldDefsController_delete","summary":"Delete a custom field definition, or archive it instead when it still has stored values or template references.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["custom-fields"]}},"/api/v1/custom-fields/{id}/bulk-fill":{"post":{"operationId":"CustomFieldDefsController_bulkFill","summary":"Set this field's value on every product in the org that doesn't already have one.","description":"Gap-fill semantics: products that already have a value are skipped, never overwritten. The value must be sent in the column matching the field's declared type. Returns filled and skipped counts.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkFillCustomFieldDto"}}}},"responses":{"201":{"description":""}},"tags":["custom-fields"]}},"/api/v1/products/{id}/custom-values":{"get":{"operationId":"CustomFieldValuesController_listProduct","summary":"List the custom field values stored directly on a product.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]},"put":{"operationId":"CustomFieldValuesController_upsertProduct","summary":"Create or update custom field values stored directly on a product.","description":"Each entry must send its value in the one column matching the field's declared type (valueText for TEXT/LONGTEXT/RICHTEXT/URL, valueNumber for NUMBER, valueBoolean for BOOLEAN, valueDate for DATE, valueJson for SELECT/MULTISELECT/JSON/ARRAY/OBJECT); a value in any other column is rejected with 400. Omitting all five value columns clears the stored value. Returns the full stored value list.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertCustomFieldValuesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]}},"/api/v1/products/{id}/resolved-custom-values":{"get":{"operationId":"CustomFieldValuesController_resolveProduct","summary":"Resolve the effective value of every active custom field for a product.","description":"Resolution order per field: the product's own value, then the product's class-template default, then none. The `source` on each entry says which level won.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]}},"/api/v1/products/{id}/custom-values/{fieldDefId}":{"delete":{"operationId":"CustomFieldValuesController_deleteProductValue","summary":"Remove one custom field's stored value from a product.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"fieldDefId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["custom-fields"]}},"/api/v1/variants/{id}/custom-values":{"get":{"operationId":"CustomFieldValuesController_listVariant","summary":"List the custom field values stored directly on a variant.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]},"put":{"operationId":"CustomFieldValuesController_upsertVariant","summary":"Create or update custom field values stored directly on a variant.","description":"Each entry must send its value in the one column matching the field's declared type (valueText for TEXT/LONGTEXT/RICHTEXT/URL, valueNumber for NUMBER, valueBoolean for BOOLEAN, valueDate for DATE, valueJson for SELECT/MULTISELECT/JSON/ARRAY/OBJECT); a value in any other column is rejected with 400. Omitting all five value columns clears the stored value. Returns the full stored value list.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertCustomFieldValuesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]}},"/api/v1/variants/{id}/resolved-custom-values":{"get":{"operationId":"CustomFieldValuesController_resolveVariant","summary":"Resolve the effective value of every active custom field for a variant.","description":"Resolution order per field: the variant's own value, then the parent product's value, then the product's class-template default, then none. The `source` on each entry says which level won.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]}},"/api/v1/variants/{id}/custom-values/{fieldDefId}":{"delete":{"operationId":"CustomFieldValuesController_deleteVariantValue","summary":"Remove one custom field's stored value from a variant.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"fieldDefId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["custom-fields"]}},"/api/v1/channel-skus/{id}/custom-values":{"get":{"operationId":"CustomFieldValuesController_listChannelSku","summary":"List the custom field values stored directly on a channel SKU.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]},"put":{"operationId":"CustomFieldValuesController_upsertChannelSku","summary":"Create or update custom field values stored directly on a channel SKU.","description":"Each entry must send its value in the one column matching the field's declared type (valueText for TEXT/LONGTEXT/RICHTEXT/URL, valueNumber for NUMBER, valueBoolean for BOOLEAN, valueDate for DATE, valueJson for SELECT/MULTISELECT/JSON/ARRAY/OBJECT); a value in any other column is rejected with 400. Omitting all five value columns clears the stored value. Returns the full stored value list.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertCustomFieldValuesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]}},"/api/v1/channel-skus/{id}/resolved-custom-values":{"get":{"operationId":"CustomFieldValuesController_resolveChannelSku","summary":"Resolve the effective value of every active custom field for a channel SKU.","description":"Resolution order per field: the channel SKU's own value, then the linked variant's value, then that variant's product value, then none. The `source` on each entry says which level won.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]}},"/api/v1/channel-skus/{id}/custom-values/{fieldDefId}":{"delete":{"operationId":"CustomFieldValuesController_deleteChannelSkuValue","summary":"Remove one custom field's stored value from a channel SKU.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"fieldDefId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["custom-fields"]}},"/api/v1/categories/{id}/custom-values":{"get":{"operationId":"CustomFieldValuesController_listCategory","summary":"List the custom field values stored on a category.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]},"put":{"operationId":"CustomFieldValuesController_upsertCategory","summary":"Create or update custom field values stored on a category.","description":"Each entry must send its value in the one column matching the field's declared type (valueText for TEXT/LONGTEXT/RICHTEXT/URL, valueNumber for NUMBER, valueBoolean for BOOLEAN, valueDate for DATE, valueJson for SELECT/MULTISELECT/JSON/ARRAY/OBJECT); a value in any other column is rejected with 400. Omitting all five value columns clears the stored value. Returns the full stored value list.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertCustomFieldValuesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]}},"/api/v1/categories/{id}/resolved-custom-values":{"get":{"operationId":"CustomFieldValuesController_resolveCategory","summary":"Get the effective value of every active custom field for a category.","description":"Categories have no inheritance chain: each value is either stored on the category itself (source `own`) or absent (source `none`).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]}},"/api/v1/categories/{id}/custom-values/{fieldDefId}":{"delete":{"operationId":"CustomFieldValuesController_deleteCategoryValue","summary":"Remove one custom field's stored value from a category.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"fieldDefId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["custom-fields"]}},"/api/v1/brands/{id}/custom-values":{"get":{"operationId":"CustomFieldValuesController_listBrand","summary":"List the custom field values stored on a brand.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]},"put":{"operationId":"CustomFieldValuesController_upsertBrand","summary":"Create or update custom field values stored on a brand.","description":"Each entry must send its value in the one column matching the field's declared type (valueText for TEXT/LONGTEXT/RICHTEXT/URL, valueNumber for NUMBER, valueBoolean for BOOLEAN, valueDate for DATE, valueJson for SELECT/MULTISELECT/JSON/ARRAY/OBJECT); a value in any other column is rejected with 400. Omitting all five value columns clears the stored value. Returns the full stored value list.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertCustomFieldValuesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]}},"/api/v1/brands/{id}/resolved-custom-values":{"get":{"operationId":"CustomFieldValuesController_resolveBrand","summary":"Get the effective value of every active custom field for a brand.","description":"Brands have no inheritance chain: each value is either stored on the brand itself (source `own`) or absent (source `none`).","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["custom-fields"]}},"/api/v1/brands/{id}/custom-values/{fieldDefId}":{"delete":{"operationId":"CustomFieldValuesController_deleteBrandValue","summary":"Remove one custom field's stored value from a brand.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"fieldDefId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["custom-fields"]}}},"info":{"title":"Foundry Partner API","description":"\nREST API for Foundry IMS partners and integrations — catalog, orders, purchase orders, stock, and outbound webhooks.\n\nAuthenticate every request with an admin API key: `Authorization: Bearer fims_...`. Keys are created in the admin UI (Settings → API Keys) with per-key permission scopes; a request needs the scope listed on the endpoint's permission gate. All routes live under `/api/v1`.\n\n**Full guide** — auth and scopes, rate limits, pagination, error shapes, webhook signature verification, and the versioning policy: https://foundryims.com/docs/partner-api\n","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"CreateEndpointDto":{"type":"object","properties":{"name":{"type":"string","maxLength":100,"description":"Label shown in the merchant's admin — name it after your app","example":"My Shipping App"},"url":{"type":"string","description":"HTTPS endpoint deliveries are POSTed to","example":"https://example.com/foundry/webhooks"},"events":{"description":"Event names to subscribe to (see GET /webhooks/events). At least one; an unknown name is rejected with a 400 that lists every valid one.","example":["order.created","inventory.availability_changed"],"type":"array","items":{"type":"string"}}},"required":["name","url","events"]},"UpdateEndpointDto":{"type":"object","properties":{"name":{"type":"string","maxLength":100,"description":"Label shown in the merchant's admin","example":"My Shipping App"},"url":{"type":"string","description":"HTTPS endpoint deliveries are POSTed to","example":"https://example.com/foundry/webhooks"},"events":{"description":"Replacement list of event names to subscribe to (see GET /webhooks/events)","example":["order.created","inventory.availability_changed"],"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean","description":"Set false to pause deliveries; set true to resume, which also clears the consecutive-failure counter behind the five-strike auto-disable","example":true}}},"CreatePurchaseOrderLineDto":{"type":"object","properties":{"variantId":{"type":"string","example":"8f1c2e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","description":"Variant id (UUID)"},"channelSkuId":{"type":"string","example":"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d","description":"Channel SKU id (UUID)"},"isCustom":{"type":"boolean","example":false,"description":"Whether this is a custom (non-catalog) line"},"description":{"type":"string","example":"Wireless Headphones - Black","description":"Line description"},"partNumber":{"type":"string","example":"WH1000-BLK","description":"Supplier part number for the line"},"quantityOrdered":{"type":"number","minimum":1,"example":100,"description":"Quantity ordered"},"unitCost":{"type":"number","minimum":0,"example":22.5,"description":"Unit cost"}},"required":["quantityOrdered","unitCost"]},"CreatePurchaseOrderDto":{"type":"object","properties":{"channelId":{"type":"string","example":"3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f","description":"Supplier channel id (UUID)"},"warehouseId":{"type":"string","example":"4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f90","description":"Destination warehouse id (UUID)"},"type":{"type":"string","enum":["WAREHOUSE","DROPSHIP"],"example":"WAREHOUSE","description":"Purchase order type"},"shippingAddress":{"type":"object","example":{"name":"Acme Inc","street1":"123 Main St","city":"Austin","state":"TX","postalCode":"78701","country":"US"},"description":"Ship-to address"},"expectedAt":{"type":"string","example":"2026-07-15","description":"Expected delivery date (ISO 8601)"},"notes":{"type":"string","example":"Please ship via ground freight","description":"Notes for the supplier"},"deliveryMethod":{"type":"string","enum":["EMAIL","DOWNLOAD","WEB"],"example":"EMAIL","description":"Delivery method"},"lines":{"description":"Purchase order line items","type":"array","items":{"$ref":"#/components/schemas/CreatePurchaseOrderLineDto"}}},"required":["channelId"]},"UpdatePurchaseOrderDto":{"type":"object","properties":{"warehouseId":{"type":"string","example":"4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f90","description":"Destination warehouse id (UUID)"},"expectedAt":{"type":"string","example":"2026-07-15","description":"Expected delivery date (ISO 8601)"},"notes":{"type":"string","example":"Please ship via ground freight","description":"Notes for the supplier"}}},"AddLineDto":{"type":"object","properties":{"variantId":{"type":"string","example":"8f1c2e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","description":"Variant id (UUID). Required unless isCustom is true"},"channelSkuId":{"type":"string","example":"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d","description":"Supplier channel SKU id (UUID)"},"isCustom":{"type":"boolean","example":false,"description":"Whether this is a custom (non-catalog) line; custom lines require a description and carry no variant"},"description":{"type":"string","example":"Wireless Headphones - Black","description":"Line description (required for custom lines)"},"partNumber":{"type":"string","example":"WH1000-BLK","description":"Supplier part number for the line"},"quantityOrdered":{"type":"number","minimum":1,"example":100,"description":"Quantity ordered"},"unitCost":{"type":"number","minimum":0,"example":22.5,"description":"Unit cost"}},"required":["quantityOrdered","unitCost"]},"UpdateLineDto":{"type":"object","properties":{"quantityOrdered":{"type":"number","minimum":1,"example":100,"description":"Quantity ordered"},"unitCost":{"type":"number","minimum":0,"example":22.5,"description":"Unit cost (also syncs the supplier channel SKU cost)"}}},"ReceiveLineDto":{"type":"object","properties":{"quantityReceived":{"type":"number","minimum":1,"example":25,"description":"Quantity received in this receipt. Added to the line's running total; cannot exceed the remaining unreceived quantity"}},"required":["quantityReceived"]},"LinkReservationsDto":{"type":"object","properties":{"reservationIds":{"example":["5e6f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9a0b"],"description":"Reservation ids (UUIDs) to link to the purchase order line for the dropship workflow","type":"array","items":{"type":"string"}}},"required":["reservationIds"]},"AddTrackingDto":{"type":"object","properties":{"carrier":{"type":"string","example":"FedEx","description":"Carrier name ('FedEx', 'UPS', 'USPS', 'DHL', etc.)"},"trackingNumber":{"type":"string","example":"123456789012","description":"Carrier tracking number"},"trackingUrl":{"type":"string","example":"https://www.fedex.com/fedextrack/?trknbr=123456789012","description":"Tracking URL; auto-generated from the carrier and tracking number if not provided"}},"required":["carrier","trackingNumber"]},"CreateInvoiceLineDto":{"type":"object","properties":{"purchaseOrderLineId":{"type":"string","example":"2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e","description":"Purchase order line id (UUID) this invoice line is matched to; must belong to the invoice's purchase order"},"vendorSku":{"type":"string","example":"WH1000-BLK","description":"The vendor's SKU as printed on the invoice"},"description":{"type":"string","example":"Wireless Headphones - Black","description":"Line description"},"quantityInvoiced":{"type":"number","minimum":1,"example":100,"description":"Quantity invoiced"},"unitCost":{"type":"number","minimum":0,"example":22.5,"description":"Unit cost on the invoice"}},"required":["quantityInvoiced","unitCost"]},"CreateInvoiceDto":{"type":"object","properties":{"purchaseOrderId":{"type":"string","example":"3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f","description":"Purchase order id (UUID) the invoice is for. Required when invoiceType is INVOICE"},"invoiceType":{"type":"string","enum":["INVOICE","CREDIT_MEMO","DEBIT_MEMO"],"example":"INVOICE","description":"Invoice type (defaults to INVOICE)"},"relatedInvoiceId":{"type":"string","example":"4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f90","description":"Id (UUID) of the invoice a credit/debit memo relates to"},"invoiceNumber":{"type":"string","example":"INV-2026-0042","description":"The vendor's invoice number"},"invoiceDate":{"type":"string","example":"2026-07-01","description":"Invoice date (ISO 8601)"},"dueDate":{"type":"string","example":"2026-07-31","description":"Payment due date (ISO 8601)"},"subtotal":{"type":"number","minimum":0,"example":2250,"description":"Subtotal; computed from the lines if omitted"},"taxAmount":{"type":"number","minimum":0,"example":180,"description":"Tax amount (defaults to 0)"},"shippingAmount":{"type":"number","minimum":0,"example":45.5,"description":"Shipping amount (defaults to 0)"},"otherCharges":{"type":"number","minimum":0,"example":10,"description":"Other charges (defaults to 0)"},"totalAmount":{"type":"number","minimum":0,"example":2485.5,"description":"Total amount; computed as subtotal + tax + shipping + other charges if omitted"},"notes":{"type":"string","example":"Backordered items shipped separately","description":"Free-form notes"},"attachmentUrl":{"type":"string","example":"https://files.example.com/invoices/INV-2026-0042.pdf","description":"URL of the invoice document attachment"},"markAsPaid":{"type":"boolean","example":false,"description":"Create the invoice already in PAID status with paidAt set to now"},"lines":{"description":"Invoice line items","type":"array","items":{"$ref":"#/components/schemas/CreateInvoiceLineDto"}}},"required":["invoiceNumber","invoiceDate"]},"UpdateInvoiceDto":{"type":"object","properties":{"invoiceNumber":{"type":"string","example":"INV-2026-0042","description":"The vendor's invoice number"},"invoiceDate":{"type":"string","example":"2026-07-01","description":"Invoice date (ISO 8601)"},"dueDate":{"type":"string","example":"2026-07-31","description":"Payment due date (ISO 8601)"},"subtotal":{"type":"number","minimum":0,"example":2250,"description":"Subtotal"},"taxAmount":{"type":"number","minimum":0,"example":180,"description":"Tax amount"},"shippingAmount":{"type":"number","minimum":0,"example":45.5,"description":"Shipping amount"},"otherCharges":{"type":"number","minimum":0,"example":10,"description":"Other charges"},"totalAmount":{"type":"number","minimum":0,"example":2485.5,"description":"Total amount"},"notes":{"type":"string","example":"Backordered items shipped separately","description":"Free-form notes"},"attachmentUrl":{"type":"string","example":"https://files.example.com/invoices/INV-2026-0042.pdf","description":"URL of the invoice document attachment"}}},"AddInvoiceLineDto":{"type":"object","properties":{"purchaseOrderLineId":{"type":"string","example":"2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e","description":"Purchase order line id (UUID) to match this invoice line to; must belong to the invoice's purchase order"},"vendorSku":{"type":"string","example":"WH1000-BLK","description":"The vendor's SKU as printed on the invoice"},"description":{"type":"string","example":"Wireless Headphones - Black","description":"Line description"},"quantityInvoiced":{"type":"number","minimum":1,"example":100,"description":"Quantity invoiced"},"unitCost":{"type":"number","minimum":0,"example":22.5,"description":"Unit cost on the invoice"}},"required":["quantityInvoiced","unitCost"]},"UpdateInvoiceLineDto":{"type":"object","properties":{"vendorSku":{"type":"string","example":"WH1000-BLK","description":"The vendor's SKU as printed on the invoice"},"description":{"type":"string","example":"Wireless Headphones - Black","description":"Line description"},"quantityInvoiced":{"type":"number","minimum":1,"example":100,"description":"Quantity invoiced"},"unitCost":{"type":"number","minimum":0,"example":22.5,"description":"Unit cost on the invoice"}}},"MatchLineDto":{"type":"object","properties":{"purchaseOrderLineId":{"type":"string","example":"2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e","description":"Purchase order line id (UUID) to match the invoice line to; must belong to the invoice's purchase order"}},"required":["purchaseOrderLineId"]},"MarkPaidDto":{"type":"object","properties":{"paidAt":{"type":"string","example":"2026-07-28","description":"Payment date (ISO 8601); defaults to now"},"paymentReference":{"type":"string","example":"CHK-10422","description":"Payment reference (check number, transaction id, etc.)"},"paymentMethod":{"type":"string","enum":["CHECK","WIRE","ACH","CREDIT_CARD","OTHER"],"example":"ACH","description":"Payment method"}}},"CreateLandedCostDto":{"type":"object","properties":{"invoiceId":{"type":"string","example":"4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f90","description":"Invoice id (UUID) the cost came from; must belong to the same purchase order"},"costType":{"type":"string","enum":["SHIPPING","TAX","DUTY","INSURANCE","HANDLING","BROKERAGE","OTHER"],"example":"SHIPPING","description":"Type of landed cost"},"description":{"type":"string","example":"Ocean freight, container share","description":"Description of the cost"},"amount":{"type":"number","minimum":0,"example":350,"description":"Cost amount"},"allocationMethod":{"type":"string","enum":["BY_VALUE","BY_QUANTITY","BY_WEIGHT","MANUAL"],"example":"BY_VALUE","description":"How the cost is spread across PO lines (defaults to BY_VALUE)"},"vendorName":{"type":"string","example":"Global Freight Co","description":"Vendor the cost was paid to"},"referenceNumber":{"type":"string","example":"BOL-88213","description":"Reference number (bill of lading, invoice number, etc.)"}},"required":["costType","amount"]},"UpdateLandedCostDto":{"type":"object","properties":{"costType":{"type":"string","enum":["SHIPPING","TAX","DUTY","INSURANCE","HANDLING","BROKERAGE","OTHER"],"example":"SHIPPING","description":"Type of landed cost"},"description":{"type":"string","example":"Ocean freight, container share","description":"Description of the cost"},"amount":{"type":"number","minimum":0,"example":350,"description":"Cost amount; changing it re-runs allocation unless the method is MANUAL"},"allocationMethod":{"type":"string","enum":["BY_VALUE","BY_QUANTITY","BY_WEIGHT","MANUAL"],"example":"BY_VALUE","description":"How the cost is spread across PO lines; changing it re-runs allocation unless set to MANUAL"},"vendorName":{"type":"string","example":"Global Freight Co","description":"Vendor the cost was paid to"},"referenceNumber":{"type":"string","example":"BOL-88213","description":"Reference number (bill of lading, invoice number, etc.)"}}},"ManualAllocationDto":{"type":"object","properties":{"purchaseOrderLineId":{"type":"string","example":"2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e","description":"Purchase order line id (UUID)"},"allocatedAmount":{"type":"number","minimum":0,"example":125.5,"description":"Amount of the landed cost allocated to this line"}},"required":["purchaseOrderLineId","allocatedAmount"]},"SetManualAllocationsDto":{"type":"object","properties":{"allocations":{"description":"Per-line allocations replacing any existing ones; amounts must sum to the landed cost amount (within $0.01)","type":"array","items":{"$ref":"#/components/schemas/ManualAllocationDto"}}},"required":["allocations"]},"ReresolveLinesDto":{"type":"object","properties":{"dryRun":{"type":"boolean","description":"Report only; write nothing","default":true},"channelId":{"type":"string","description":"Limit to one channel"},"orderId":{"type":"string","description":"Limit to one order"},"limit":{"type":"number","minimum":1,"maximum":5000,"default":500,"description":"Maximum lines to examine"}}},"ReresolvedLineDto":{"type":"object","properties":{"orderNumber":{"type":"string","nullable":true,"description":"Channel order number of the order the line belongs to"},"sku":{"type":"string","nullable":true,"description":"The SKU as the order line carries it"},"via":{"type":"string","description":"How the line matched","enum":["channelVariantId","sku","foundrySku","skuBase","unmatched","ambiguous"]},"matchedSku":{"type":"string","description":"For a skuBase match: the catalogue SKU actually paired to after stripping the listing suffix — the one inferred step in the chain, surfaced so it can be spot-checked"},"variantId":{"type":"string","nullable":true,"description":"Catalog variant the line resolved to; null when unmatched"},"linked":{"type":"boolean","description":"False on a dry run"},"reserved":{"type":"boolean","description":"Whether the line holds at least one reservation after this run (observed, not predicted). Always false on a dry run."},"note":{"type":"string","description":"Why the line could not be linked or reserved, when it could not"}},"required":["orderNumber","sku","via","variantId","linked","reserved"]},"ReresolveLinesResultDto":{"type":"object","properties":{"dryRun":{"type":"boolean","description":"True when nothing was written — the run only reported what it would do"},"examined":{"type":"number","description":"Unlinked lines examined this run"},"matched":{"type":"number","description":"Lines that resolved to a catalog variant"},"unmatched":{"type":"number","description":"Lines no catalog variant could be found for"},"reservationsCreated":{"type":"number","description":"Reservations actually created by this run"},"truncated":{"type":"boolean","description":"True when the run hit its line limit — run again for the rest"},"lines":{"description":"Per-line outcome for every examined line","type":"array","items":{"$ref":"#/components/schemas/ReresolvedLineDto"}}},"required":["dryRun","examined","matched","unmatched","reservationsCreated","truncated","lines"]},"BulkUpdateOrderStatusDto":{"type":"object","properties":{"status":{"type":"string","description":"Status to move the order to","enum":["PENDING","PROCESSING","AWAITING_FULFILLMENT","AWAITING_SHIPMENT","PARTIALLY_SHIPPED","SHIPPED","COMPLETED","CANCELLED","REFUNDED","PARTIALLY_REFUNDED","ON_HOLD"],"example":"SHIPPED"},"note":{"type":"string","maxLength":1000,"description":"Note recorded in the order's status history","example":"Customer picked up in person"},"orderIds":{"description":"IDs of the orders to update — every order gets the same status and note","example":["0d9b1d5e-7c2a-4f6e-9b1a-2c3d4e5f6a7b"],"type":"array","items":{"type":"string"}}},"required":["status","orderIds"]},"UpdateOrderStatusDto":{"type":"object","properties":{"status":{"type":"string","description":"Status to move the order to","enum":["PENDING","PROCESSING","AWAITING_FULFILLMENT","AWAITING_SHIPMENT","PARTIALLY_SHIPPED","SHIPPED","COMPLETED","CANCELLED","REFUNDED","PARTIALLY_REFUNDED","ON_HOLD"],"example":"SHIPPED"},"note":{"type":"string","maxLength":1000,"description":"Note recorded in the order's status history","example":"Customer picked up in person"}},"required":["status"]},"FulfillOrderDto":{"type":"object","properties":{"warehouseId":{"type":"string","description":"Warehouse the goods shipped from"},"markShipped":{"type":"boolean","description":"Also set the order status to SHIPPED","default":true}}},"AdjustStockDto":{"type":"object","properties":{"variantId":{"type":"string","description":"Variant whose stock is being adjusted","example":"0d9b1d5e-7c2a-4f6e-9b1a-2c3d4e5f6a7b"},"warehouseId":{"type":"string","description":"Warehouse the adjustment applies to","example":"7b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"},"quantity":{"type":"number","description":"Signed change to on-hand quantity: positive adds stock, negative removes it","example":-3},"reason":{"type":"string","enum":["CYCLE_COUNT","DAMAGE","THEFT","FOUND","CORRECTION","OTHER"],"description":"Why the adjustment was made — recorded on the resulting stock movement","example":"CYCLE_COUNT"},"notes":{"type":"string","description":"Free-text note recorded on the resulting stock movement","example":"Two units water-damaged in receiving"},"binId":{"type":"string","nullable":true,"description":"Bin location to adjust. Omitted: resolves automatically (primary bin, else the single bin, else the fullest). Explicit null: the Unassigned location. A bin id: that exact bin.","example":"3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"},"allowNegative":{"type":"boolean","description":"Confirm an adjustment that would take the target location below zero on-hand; refused without it","default":false,"example":false}},"required":["variantId","warehouseId","quantity","reason"]},"SetPrimaryBinDto":{"type":"object","properties":{"variantId":{"type":"string","description":"Variant (SKU) whose primary bin is being set","example":"0d9b1d5e-7c2a-4f6e-9b1a-2c3d4e5f6a7b"},"warehouseId":{"type":"string","description":"Warehouse the primary-bin designation applies to (each warehouse has its own)","example":"7b1a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"},"binId":{"type":"string","description":"Bin to designate as primary — the SKU must already have a stock location in this bin","example":"3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"}},"required":["variantId","warehouseId","binId"]},"CountLineDto":{"type":"object","properties":{"sku":{"type":"string","description":"The SKU counted — matched by foundrySku first, falling back to the org's own sku","example":"CPL0122"},"bin":{"type":"string","description":"Bin NAME the count was taken in; blank or omitted means the Unassigned location","example":"A-01-03"},"counted":{"type":"number","description":"Quantity physically counted at that location","example":12},"primary":{"type":"boolean","description":"True makes this bin the SKU's primary bin; absent leaves the primary as-is","example":true}},"required":["sku","counted"]},"BulkCountPreviewDto":{"type":"object","properties":{"warehouseId":{"type":"string","description":"Populated from the :warehouseId path parameter — do not send it in the body"},"lines":{"description":"The count lines to diff against current on-hand quantities","type":"array","items":{"$ref":"#/components/schemas/CountLineDto"}}},"required":["lines"]},"CountApplyLineDto":{"type":"object","properties":{"variantId":{"type":"string","description":"Variant the line applies to; either variantId or sku must identify the line","example":"0d9b1d5e-7c2a-4f6e-9b1a-2c3d4e5f6a7b"},"sku":{"type":"string","description":"Alternative to variantId: the SKU, matched by foundrySku first, falling back to the org's own sku — resolved the same way preview does","example":"CPL0122"},"binId":{"type":"string","nullable":true,"description":"Bin location the count applies to; null or omitted means the Unassigned location","example":"3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"},"counted":{"type":"number","description":"Quantity physically counted at that location","example":12},"primary":{"type":"boolean","description":"True makes this bin the SKU's primary bin; absent leaves the primary as-is","example":true}},"required":["counted"]},"BulkCountApplyDto":{"type":"object","properties":{"warehouseId":{"type":"string","description":"Populated from the :warehouseId path parameter — do not send it in the body"},"lines":{"description":"The count lines to apply as stock adjustments","type":"array","items":{"$ref":"#/components/schemas/CountApplyLineDto"}},"notes":{"type":"string","description":"Free-text note recorded on the resulting stock movements","example":"Q3 cycle count, aisle A"},"scope":{"type":"string","enum":["FULL","PARTIAL"],"description":"PARTIAL (the default) touches only the lines listed; FULL additionally zeros every stocked location in the warehouse that the sheet does not mention","default":"PARTIAL","example":"PARTIAL"}},"required":["lines"]},"CreateProductDto":{"type":"object","properties":{"name":{"type":"string","maxLength":255,"example":"Wireless Headphones","description":"Product display name"},"sku":{"type":"string","description":"Team-facing SKU for the default variant","maxLength":255,"example":"WH-1000"},"slug":{"type":"string","maxLength":255,"example":"wireless-headphones","description":"URL handle for storefronts"},"description":{"type":"string","example":"Over-ear Bluetooth headphones with active noise cancellation","description":"Public product description"},"metaTitle":{"type":"string","nullable":true,"maxLength":255,"example":"Wireless Headphones | Acme","description":"SEO meta title; blank falls back to the product name"},"metaDescription":{"type":"string","nullable":true,"maxLength":500,"example":"Over-ear Bluetooth headphones with ANC.","description":"SEO meta description; blank falls back to a description excerpt"},"internalDescription":{"type":"string","example":"Top seller; reorder when stock < 50","description":"Internal-only notes"},"featured":{"type":"boolean","example":false,"description":"Whether the product is featured"},"displayOrder":{"type":"number","example":0,"description":"Sort order for display"},"type":{"type":"string","enum":["SIMPLE","FAMILY","BUNDLE","ASSEMBLY"],"example":"SIMPLE","description":"Product type"},"brandId":{"type":"string","example":"8f1c2e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","description":"Brand id (UUID)"},"categoryId":{"type":"string","example":"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d","description":"Category id (UUID)"},"productClassId":{"type":"string","nullable":true,"example":"2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e","description":"Product class id (UUID), or null"},"customFields":{"type":"object","example":{"color":"black","warrantyMonths":24},"description":"Arbitrary custom fields"}},"required":["name"]},"DuplicateProductDto":{"type":"object","properties":{"name":{"type":"string","description":"Name for the copy; defaults to \"<original name> (copy)\"","maxLength":500,"example":"Wireless Headphones (copy)"},"sku":{"type":"string","description":"Team-facing SKU for the copy's variant; left blank when omitted (a fresh Foundry SKU is always generated)","maxLength":200,"example":"WH-1000-V2"}}},"UpdateProductDto":{"type":"object","properties":{"name":{"type":"string","maxLength":255,"example":"Wireless Headphones","description":"Product display name"},"slug":{"type":"string","maxLength":255,"example":"wireless-headphones","description":"URL handle for storefronts"},"description":{"type":"string","example":"Over-ear Bluetooth headphones with active noise cancellation","description":"Public product description"},"metaTitle":{"type":"string","nullable":true,"maxLength":255,"example":"Wireless Headphones | Acme","description":"SEO meta title; blank falls back to the product name"},"metaDescription":{"type":"string","nullable":true,"maxLength":500,"example":"Over-ear Bluetooth headphones with ANC.","description":"SEO meta description; blank falls back to a description excerpt"},"internalDescription":{"type":"string","example":"Top seller; reorder when stock < 50","description":"Internal-only notes"},"featured":{"type":"boolean","example":false,"description":"Whether the product is featured"},"displayOrder":{"type":"number","example":0,"description":"Sort order for display"},"type":{"type":"string","enum":["SIMPLE","FAMILY","BUNDLE","ASSEMBLY"],"example":"SIMPLE","description":"Product type"},"brandId":{"type":"string","example":"8f1c2e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","description":"Brand id (UUID)"},"categoryId":{"type":"string","example":"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d","description":"Category id (UUID)"},"productClassId":{"type":"string","nullable":true,"example":"2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e","description":"Product class id (UUID), or null"},"customFields":{"type":"object","example":{"color":"black","warrantyMonths":24},"description":"Arbitrary custom fields"}}},"CreateVariantParentDto":{"type":"object","properties":{"name":{"type":"string","maxLength":255,"example":"Wireless Headphones","description":"Display name for the new FAMILY parent product"},"description":{"type":"string","example":"Over-ear Bluetooth headphones, all colors","description":"Public description for the parent product"},"brandId":{"type":"string","example":"8f1c2e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","description":"Brand id (UUID) for the parent product"},"categoryId":{"type":"string","example":"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d","description":"Category id (UUID) for the parent product"},"childProductIds":{"example":["3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"],"description":"Ids (UUID) of existing products to link as children; each must not already belong to another family","type":"array","items":{"type":"string"}}},"required":["name","childProductIds"]},"LinkChildrenDto":{"type":"object","properties":{"productIds":{"example":["3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"],"description":"Ids (UUID) of products to link as children of the parent","type":"array","items":{"type":"string"}}},"required":["productIds"]},"ReorderChildrenDto":{"type":"object","properties":{"orderedChildIds":{"example":["3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f"],"description":"Child product ids (UUID) in the desired display order; each child's position becomes its index in this array, and children not listed keep their existing position","type":"array","items":{"type":"string"}}},"required":["orderedChildIds"]},"ConvertProductDto":{"type":"object","properties":{"targetType":{"type":"string","enum":["SIMPLE","FAMILY","BUNDLE","ASSEMBLY"],"example":"BUNDLE","description":"Product type to convert to"},"acknowledgeWarnings":{"type":"boolean","description":"Set true to proceed past warn-only preflight findings (links attached, stock to zero, …)"}},"required":["targetType"]},"BulkProductDefinition":{"type":"object","properties":{"name":{"type":"string","example":"Wireless Headphones","description":"Product display name"},"description":{"type":"string","example":"Over-ear Bluetooth headphones","description":"Public product description"},"brandId":{"type":"string","example":"8f1c2e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","description":"Brand id (UUID); derived from the first source SKU that carries a brand when omitted"},"categoryId":{"type":"string","example":"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d","description":"Category id (UUID)"},"type":{"type":"string","enum":["SIMPLE","FAMILY","BUNDLE","ASSEMBLY"],"example":"SIMPLE","description":"SIMPLE (one product with one variant) or FAMILY (a parent with one child variant per source SKU)"},"channelSkuIds":{"example":["5e6f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9a0b"],"description":"Source channel SKU ids (UUID) to link; each must exist and not already be linked to a variant","type":"array","items":{"type":"string"}}},"required":["name","type","channelSkuIds"]},"BulkCreateFromSourceDto":{"type":"object","properties":{"products":{"description":"Product definitions to create; each is processed independently and per-product failures are reported in `errors`","type":"array","items":{"$ref":"#/components/schemas/BulkProductDefinition"}}},"required":["products"]},"CreateVariantDto":{"type":"object","properties":{"productId":{"type":"string","example":"8f1c2e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","description":"Parent product id (UUID)"},"sku":{"type":"string","nullable":true,"maxLength":100,"example":"WH-1000-BLK","description":"Team-facing SKU; blank/null falls back to the Foundry SKU"},"name":{"type":"string","maxLength":255,"example":"Wireless Headphones - Black","description":"Variant display name"},"upc":{"type":"string","example":"012345678905","description":"UPC barcode"},"gtin":{"type":"string","example":"00012345678905","description":"GTIN barcode"},"mpn":{"type":"string","example":"WH1000-BLK","description":"Manufacturer part number"},"cost":{"type":"number","minimum":0,"example":22.5,"description":"Unit cost"},"price":{"type":"number","minimum":0,"example":49.99,"description":"Selling price"},"compareAtPrice":{"type":"number","nullable":true,"minimum":0,"example":59.99,"description":"Compare-at / MSRP price for sale display"},"weight":{"type":"number","minimum":0,"example":0.35,"description":"Weight"},"length":{"type":"number","minimum":0,"example":20,"description":"Length"},"width":{"type":"number","minimum":0,"example":18,"description":"Width"},"height":{"type":"number","minimum":0,"example":8,"description":"Height"},"condition":{"type":"string","enum":["NEW","REFURBISHED","USED","OPEN_BOX"],"example":"NEW","description":"Product condition"},"images":{"example":["https://cdn.example.com/wh-1000-black.jpg"],"description":"Image URLs","type":"array","items":{"type":"string"}},"customFields":{"type":"object","example":{"color":"black"},"description":"Arbitrary custom fields"},"trackInventory":{"type":"boolean","example":true,"description":"Whether inventory is tracked for this variant"},"isRawMaterial":{"type":"boolean","example":false,"description":"Whether this variant is a raw material"},"stockUom":{"type":"string","enum":["ea","dz","pair","mg","g","kg","oz","lb","ml","cl","L","fl_oz","cup","pt","qt","gal","mm","cm","m","in","ft","yd","bag","box","pack","roll","set","sheet","label"],"example":"EA","description":"Stock unit of measure"},"safetyStockQty":{"type":"number","minimum":0,"example":10,"description":"Safety stock quantity"},"targetStockQty":{"type":"number","minimum":0,"example":10,"description":"Build-to / target stock level"},"reorderPoint":{"type":"number","nullable":true,"minimum":0,"example":5,"description":"Build-queue trigger: only queue a below-target build once supply drops under this. Null = trigger at targetStockQty."}},"required":["productId","name"]},"UpdateVariantDto":{"type":"object","properties":{"sku":{"type":"string","nullable":true,"maxLength":100,"example":"WH-1000-BLK","description":"Team-facing SKU; blank/null falls back to the Foundry SKU"},"name":{"type":"string","maxLength":255,"example":"Wireless Headphones - Black","description":"Variant display name"},"upc":{"type":"string","example":"012345678905","description":"UPC barcode"},"gtin":{"type":"string","example":"00012345678905","description":"GTIN barcode"},"mpn":{"type":"string","example":"WH1000-BLK","description":"Manufacturer part number"},"cost":{"type":"number","minimum":0,"example":22.5,"description":"Unit cost"},"price":{"type":"number","minimum":0,"example":49.99,"description":"Selling price"},"compareAtPrice":{"type":"number","nullable":true,"minimum":0,"example":59.99,"description":"Compare-at / MSRP price for sale display"},"map":{"type":"number","nullable":true,"minimum":0,"example":44.99,"description":"Minimum advertised price"},"weight":{"type":"number","minimum":0,"example":0.35,"description":"Weight"},"length":{"type":"number","minimum":0,"example":20,"description":"Length"},"width":{"type":"number","minimum":0,"example":18,"description":"Width"},"height":{"type":"number","minimum":0,"example":8,"description":"Height"},"condition":{"type":"string","enum":["NEW","REFURBISHED","USED","OPEN_BOX"],"example":"NEW","description":"Product condition"},"images":{"example":["https://cdn.example.com/wh-1000-black.jpg"],"description":"Image URLs","type":"array","items":{"type":"string"}},"customFields":{"type":"object","example":{"color":"black"},"description":"Arbitrary custom fields"},"trackInventory":{"type":"boolean","example":true,"description":"Whether inventory is tracked for this variant"},"isRawMaterial":{"type":"boolean","example":false,"description":"Whether this variant is a raw material"},"stockUom":{"type":"string","enum":["ea","dz","pair","mg","g","kg","oz","lb","ml","cl","L","fl_oz","cup","pt","qt","gal","mm","cm","m","in","ft","yd","bag","box","pack","roll","set","sheet","label"],"example":"EA","description":"Stock unit of measure"},"safetyStockQty":{"type":"number","minimum":0,"example":10,"description":"Safety stock quantity"},"targetStockQty":{"type":"number","minimum":0,"example":10,"description":"Build-to / target stock level"},"reorderPoint":{"type":"number","nullable":true,"minimum":0,"example":5,"description":"Build-queue trigger: only queue a below-target build once supply drops under this. Null = trigger at targetStockQty."},"costSourceChannelSkuId":{"type":"string","nullable":true,"example":"8f1c2e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","description":"Pin this ChannelSku as the primary cost source; null clears the pin"}}},"AddComponentDto":{"type":"object","properties":{"componentVariantId":{"type":"string","example":"8f1c2e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","description":"Id (UUID) of the variant to include in the bundle"},"quantity":{"type":"number","default":1,"minimum":1,"example":2,"description":"How many of that variant one bundle contains (defaults to 1)"}},"required":["componentVariantId"]},"UpdateQuantityDto":{"type":"object","properties":{"quantity":{"type":"number","minimum":1,"example":2,"description":"New per-bundle quantity for the component"}},"required":["quantity"]},"CreateBrandDto":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"description":"Brand name.","example":"Fiamma"},"slug":{"type":"string","maxLength":200,"description":"Storefront slug; derived from name (unique per org) when omitted.","example":"fiamma"},"description1":{"type":"string","nullable":true,"maxLength":100000,"description":"Primary rich-text (HTML) description; sanitized on write."},"description2":{"type":"string","nullable":true,"maxLength":100000,"description":"Secondary rich-text (HTML) description; sanitized on write."},"metaTitle":{"type":"string","nullable":true,"maxLength":255,"description":"SEO meta title (plain text); blank falls back to the brand name."},"metaDescription":{"type":"string","nullable":true,"maxLength":500,"description":"SEO meta description (plain text); blank falls back to a description excerpt."}},"required":["name"]},"UpdateBrandDto":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"description":"New brand name."},"slug":{"type":"string","maxLength":200,"description":"New storefront slug (unique per org)."},"description1":{"type":"string","nullable":true,"maxLength":100000,"description":"Primary rich-text (HTML) description; sanitized on write."},"description2":{"type":"string","nullable":true,"maxLength":100000,"description":"Secondary rich-text (HTML) description; sanitized on write."},"metaTitle":{"type":"string","nullable":true,"maxLength":255,"description":"SEO meta title (plain text); blank falls back to the brand name."},"metaDescription":{"type":"string","nullable":true,"maxLength":500,"description":"SEO meta description (plain text); blank falls back to a description excerpt."}}},"BulkIdsDto":{"type":"object","properties":{"ids":{"description":"Ids of the records to act on.","type":"array","items":{"type":"string"}}},"required":["ids"]},"MergeBrandDto":{"type":"object","properties":{"targetBrandId":{"type":"string","description":"Brand that absorbs the source brand's products; the source brand is deleted."}},"required":["targetBrandId"]},"CreateCategoryDto":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"description":"Category name.","example":"Awnings"},"slug":{"type":"string","maxLength":200,"description":"Storefront slug; derived from name (unique per org) when omitted.","example":"awnings"},"parentId":{"type":"string","description":"Parent category id for hierarchical taxonomies; omit for a root category."},"description1":{"type":"string","nullable":true,"maxLength":100000,"description":"Primary rich-text (HTML) description; sanitized on write."},"description2":{"type":"string","nullable":true,"maxLength":100000,"description":"Secondary rich-text (HTML) description; sanitized on write."},"metaTitle":{"type":"string","nullable":true,"maxLength":255,"description":"SEO meta title (plain text); blank falls back to the category name."},"metaDescription":{"type":"string","nullable":true,"maxLength":500,"description":"SEO meta description (plain text); blank falls back to a description excerpt."}},"required":["name"]},"ReorderCategoriesDto":{"type":"object","properties":{"parentId":{"type":"string","nullable":true,"description":"The parent whose children are being reordered; null/omitted = root level."},"orderedIds":{"description":"Category ids in the desired display order. Position is derived from this array — Category.position is read-only on item PUT.","type":"array","items":{"type":"string"}}},"required":["orderedIds"]},"UpdateCategoryDto":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"description":"Category name.","example":"Awnings"},"slug":{"type":"string","maxLength":200,"description":"Storefront slug; derived from name (unique per org) when omitted.","example":"awnings"},"parentId":{"type":"string","description":"Parent category id for hierarchical taxonomies; omit for a root category."},"description1":{"type":"string","nullable":true,"maxLength":100000,"description":"Primary rich-text (HTML) description; sanitized on write."},"description2":{"type":"string","nullable":true,"maxLength":100000,"description":"Secondary rich-text (HTML) description; sanitized on write."},"metaTitle":{"type":"string","nullable":true,"maxLength":255,"description":"SEO meta title (plain text); blank falls back to the category name."},"metaDescription":{"type":"string","nullable":true,"maxLength":500,"description":"SEO meta description (plain text); blank falls back to a description excerpt."}},"required":["name"]},"CreateWarehouseDto":{"type":"object","properties":{"name":{"type":"string","description":"Warehouse name.","example":"Main Warehouse"},"address":{"type":"object","description":"Free-form address map; no shape is enforced.","additionalProperties":{"type":"string"},"example":{"line1":"123 Main St","city":"Portland","state":"OR","zip":"97201"}}},"required":["name"]},"UpdateWarehouseDto":{"type":"object","properties":{"name":{"type":"string","description":"New warehouse name."},"address":{"type":"object","nullable":true,"description":"Free-form address map; no shape is enforced. Null clears the stored address.","additionalProperties":{"type":"string"}},"isActive":{"type":"boolean","description":"Whether the warehouse is active."}}},"CreateBinDto":{"type":"object","properties":{"name":{"type":"string","description":"Bin label, unique (case-insensitive) within its warehouse.","example":"A-1"}},"required":["name"]},"UpdateBinDto":{"type":"object","properties":{"name":{"type":"string","description":"New bin label, unique (case-insensitive) within its warehouse.","example":"A-2"}},"required":["name"]},"CreateOptionTypeDto":{"type":"object","properties":{"name":{"type":"string","description":"Option type name, unique per org (e.g. \"Size\", \"Color\").","example":"Color"},"position":{"type":"number","description":"Sort position among the org's option types; appended at the end when omitted."}},"required":["name"]},"UpdateOptionTypeDto":{"type":"object","properties":{"name":{"type":"string","description":"New option type name."},"position":{"type":"number","description":"New sort position among the org's option types."}}},"CreateProductOptionDto":{"type":"object","properties":{"optionTypeId":{"type":"string","description":"Global option type to link this product option to; must exist in the org.","format":"uuid"},"name":{"type":"string","description":"Option name as shown on this product (e.g. \"Size\").","example":"Size"},"position":{"type":"number","description":"Sort position among the product's options; appended at the end when omitted."}},"required":["name"]},"UpdateProductOptionDto":{"type":"object","properties":{"name":{"type":"string","description":"New option name."},"position":{"type":"number","description":"New sort position among the product's options."}}},"SetVariantOptionValueDto":{"type":"object","properties":{"productOptionId":{"type":"string","description":"Product option this value is for; must be an option on the variant's product or its parent product.","format":"uuid"},"value":{"type":"string","description":"Value of the option for this variant (e.g. \"Large\").","example":"Large"}},"required":["productOptionId","value"]},"SetVariantOptionsDto":{"type":"object","properties":{"options":{"description":"The variant's complete set of option values; replaces all existing values.","type":"array","items":{"$ref":"#/components/schemas/SetVariantOptionValueDto"}}},"required":["options"]},"CreateChannelSkuDto":{"type":"object","properties":{"channelId":{"type":"string","description":"Id of the channel (source or storefront) this SKU belongs to."},"variantId":{"type":"string","description":"Id of the catalog variant to link this SKU to. Omit to create it unmapped and link later."},"channelSku":{"type":"string","description":"The SKU identifier as the channel knows it. Unique per channel (duplicates return 409).","example":"ABC-123"},"channelProductId":{"type":"string","description":"The channel's own product id for this listing."},"channelVariantId":{"type":"string","description":"The channel's own variant id for this listing."},"cost":{"type":"number","minimum":0,"description":"Supplier unit cost.","example":12.5},"moq":{"type":"number","minimum":1,"description":"Minimum order quantity from this supplier. Defaults to 1.","example":1},"packSize":{"type":"number","minimum":1,"description":"Units per pack when ordering from this supplier. Defaults to 1.","example":1},"leadTimeDays":{"type":"number","minimum":0,"description":"Supplier lead time in days."},"inventoryQuantity":{"type":"number","minimum":0,"description":"Quantity available at the channel (e.g. the supplier's stock). Defaults to 0."},"price":{"type":"number","minimum":0,"description":"Regular selling price on this channel.","example":99.99},"salePrice":{"type":"number","nullable":true,"description":"Temporary sale price for this listing. Must be below the regular price (and at or above MAP when the org enforces MAP). Null clears the sale and restores the regular price.","minimum":0.01,"example":79.99},"saleStartsAt":{"type":"string","nullable":true,"description":"When the sale starts. Null = immediately.","example":"2026-09-01T00:00:00Z"},"saleEndsAt":{"type":"string","nullable":true,"description":"When the sale ends and the regular price returns. Null = until cleared.","example":"2026-09-15T23:59:00Z"},"isListed":{"type":"boolean","description":"Whether this SKU is actively listed on the channel. Defaults to false."},"title":{"type":"string","description":"Listing title as it appears on the channel."},"description":{"type":"string","description":"Listing description as it appears on the channel."},"brand":{"type":"string","description":"Brand name."},"gtin":{"type":"string","description":"GTIN barcode."},"mpn":{"type":"string","description":"Manufacturer part number."},"imageUrl":{"type":"string","pattern":"/^(https?:\\/\\/|data:image\\/)/","description":"Primary image: an http(s) URL or an inline data:image/* URI.","example":"https://example.com/images/abc-123.jpg"}},"required":["channelId","channelSku"]},"UpdateChannelSkuDto":{"type":"object","properties":{"variantId":{"type":"string","description":"Id of the catalog variant to link this SKU to."},"channelSku":{"type":"string","description":"The SKU identifier as the channel knows it. Unique per channel."},"channelProductId":{"type":"string","description":"The channel's own product id for this listing."},"channelVariantId":{"type":"string","description":"The channel's own variant id for this listing."},"cost":{"type":"number","minimum":0,"description":"Supplier unit cost.","example":12.5},"map":{"type":"number","minimum":0,"description":"Minimum advertised price (MAP) for this SKU."},"compareAtPrice":{"type":"number","nullable":true,"minimum":0,"description":"MSRP shown as the channel's strikethrough price. Null clears it on the channel.","example":99.95},"moq":{"type":"number","minimum":1,"description":"Minimum order quantity from this supplier.","example":1},"packSize":{"type":"number","minimum":1,"description":"Units per pack when ordering from this supplier.","example":1},"leadTimeDays":{"type":"number","minimum":0,"description":"Supplier lead time in days."},"inventoryQuantity":{"type":"number","minimum":0,"description":"Quantity available at the channel (e.g. the supplier's stock)."},"price":{"type":"number","minimum":0,"description":"Regular selling price on this channel.","example":99.99},"salePrice":{"type":"number","nullable":true,"description":"Temporary sale price for this listing. Must be below the regular price (and at or above MAP when the org enforces MAP). Null clears the sale and restores the regular price.","minimum":0.01,"example":79.99},"saleStartsAt":{"type":"string","nullable":true,"description":"When the sale starts. Null = immediately.","example":"2026-09-01T00:00:00Z"},"saleEndsAt":{"type":"string","nullable":true,"description":"When the sale ends and the regular price returns. Null = until cleared.","example":"2026-09-15T23:59:00Z"},"isListed":{"type":"boolean","description":"Whether this SKU is actively listed on the channel."},"title":{"type":"string","description":"Listing title as it appears on the channel."},"description":{"type":"string","description":"Listing description as it appears on the channel."},"brand":{"type":"string","description":"Brand name."},"upc":{"type":"string","description":"UPC barcode."},"gtin":{"type":"string","description":"GTIN barcode."},"mpn":{"type":"string","description":"Manufacturer part number."},"imageUrl":{"type":"string","pattern":"/^(https?:\\/\\/|data:image\\/)/","description":"Primary image: an http(s) URL or an inline data:image/* URI.","example":"https://example.com/images/abc-123.jpg"}}},"LinkChannelSkuDto":{"type":"object","properties":{"variantId":{"type":"string","description":"Id of the catalog variant to link the channel SKU to."}},"required":["variantId"]},"ImportChannelSkuDto":{"type":"object","properties":{"channelId":{"type":"string","description":"Id of the channel to import the CSV rows into."},"delimiter":{"type":"string","description":"CSV delimiter character when it is not a comma (e.g. \";\" or a tab).","example":";"}},"required":["channelId"]},"CreateCustomFieldDefDto":{"type":"object","properties":{"key":{"type":"string","description":"Machine key, unique per org. Must start with a lowercase letter and contain only lowercase letters, digits, or underscores. Immutable after creation.","example":"country_of_origin"},"label":{"type":"string","description":"Human-readable label shown in the UI.","example":"Country of Origin"},"description":{"type":"string","description":"Help text describing what the field is for."},"type":{"type":"string","enum":["TEXT","LONGTEXT","RICHTEXT","NUMBER","BOOLEAN","SELECT","MULTISELECT","DATE","URL","JSON","ARRAY","OBJECT"],"description":"Value type of the field. Determines which value column stores its values. Immutable after creation.","example":"TEXT"},"appliesTo":{"type":"array","enum":["PRODUCT","VARIANT","CHANNEL_SKU","CATEGORY","BRAND"],"description":"Which catalog entities this field may be attached to.","example":["PRODUCT","VARIANT"],"items":{"type":"string","enum":["PRODUCT","VARIANT","CHANNEL_SKU","CATEGORY","BRAND"]}},"config":{"type":"object","additionalProperties":true,"description":"Type-specific configuration, e.g. the option list for SELECT/MULTISELECT fields."},"validation":{"type":"object","additionalProperties":true,"description":"Validation rules applied to values of this field."},"isGlobal":{"type":"boolean","description":"Whether the field is part of the global field library. Defaults to false."},"visibleOnStorefront":{"type":"boolean","description":"Expose this field's value on the public Storefront (headless) API."},"storefrontGroup":{"type":"string","nullable":true,"description":"Storefront spec-table section this field is grouped under.","example":"Performance"},"storefrontOrder":{"type":"number","description":"Sort order within the storefront spec table (ascending)."}},"required":["key","label","type"]},"UpdateCustomFieldDefDto":{"type":"object","properties":{"label":{"type":"string","description":"Human-readable label shown in the UI."},"description":{"type":"string","description":"Help text describing what the field is for."},"config":{"type":"object","additionalProperties":true,"description":"Type-specific configuration, e.g. the option list for SELECT/MULTISELECT fields."},"validation":{"type":"object","additionalProperties":true,"description":"Validation rules applied to values of this field."},"archived":{"type":"boolean","description":"Archive (true) or unarchive (false) the field. Archived fields are hidden from listings and resolution."},"appliesTo":{"type":"array","enum":["PRODUCT","VARIANT","CHANNEL_SKU","CATEGORY","BRAND"],"description":"Which catalog entities this field may be attached to.","example":["PRODUCT","VARIANT"],"items":{"type":"string","enum":["PRODUCT","VARIANT","CHANNEL_SKU","CATEGORY","BRAND"]}},"isGlobal":{"type":"boolean","description":"Whether the field is part of the global field library."},"visibleOnStorefront":{"type":"boolean","description":"Expose this field's value on the public Storefront (headless) API."},"storefrontGroup":{"type":"string","nullable":true,"description":"Storefront spec-table section this field is grouped under. Pass null to clear.","example":"Performance"},"storefrontOrder":{"type":"number","description":"Sort order within the storefront spec table (ascending)."}}},"BulkFillCustomFieldDto":{"type":"object","properties":{"valueText":{"type":"string","nullable":true,"description":"Value for TEXT, LONGTEXT, RICHTEXT, and URL fields. Send the value in the one column matching the field's declared type."},"valueNumber":{"type":"number","nullable":true,"description":"Value for NUMBER fields."},"valueBoolean":{"type":"boolean","nullable":true,"description":"Value for BOOLEAN fields."},"valueDate":{"type":"string","nullable":true,"description":"Value for DATE fields (ISO 8601).","example":"2026-08-30"},"valueJson":{"type":"object","description":"Value for SELECT, MULTISELECT, JSON, ARRAY, and OBJECT fields."}}},"CustomFieldValueEntryDto":{"type":"object","properties":{"fieldDefId":{"type":"string","description":"Id of the custom field definition this value belongs to."},"valueText":{"type":"string","nullable":true,"description":"Value for TEXT, LONGTEXT, RICHTEXT, and URL fields. Send the value in the ONE column matching the field's declared type — any other column is rejected with 400. Omit all five value columns to clear the value."},"valueNumber":{"type":"number","nullable":true,"description":"Value for NUMBER fields."},"valueBoolean":{"type":"boolean","nullable":true,"description":"Value for BOOLEAN fields."},"valueDate":{"type":"string","nullable":true,"description":"Value for DATE fields (ISO 8601).","example":"2026-08-30"},"valueJson":{"type":"object","description":"Value for SELECT, MULTISELECT, JSON, ARRAY, and OBJECT fields."}},"required":["fieldDefId"]},"UpsertCustomFieldValuesDto":{"type":"object","properties":{"values":{"description":"Values to create or update, one entry per custom field.","type":"array","items":{"$ref":"#/components/schemas/CustomFieldValueEntryDto"}}},"required":["values"]}}}}